<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="./assets/global.css">


    <style>
        @font-face {
            font-family: "SourceHanSansCN-Light";
            src: url('./assets/SourceHanSansCN-Light.otf');
        }


        body {
            height: 100vh;
            font-family: "SourceHanSansCN-Light";
        }

        .flex-row-center {
            display: inline-flex;
            align-items: center;
            justify-content: center;
        }

        .flex-column-center {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            flex-direction: column;
        }


        .game-container {
            position: relative;
            overflow: hidden;
            width: 100vw;
            height: 100vh;
            user-select: none;
        }

        .game-btn-confirm {
            display: inline-flex;
            padding: 0 20px 3px;
            line-height: 40px;
            background: linear-gradient(to bottom, rgb(87, 196, 245), rgb(26, 147, 206));
            color: rgb(254, 252, 255);
            cursor: pointer;
            border-radius: 4px;
            font-weight: bold;
            box-shadow: inset 0px -3px 0 rgb(19, 98, 139);
        }

        .game-btn-confirm:active {
            opacity: .7;
            box-shadow: inset 0px 0px 0 transparent;
        }

        object {
            pointer-events: none;
        }
    </style>
</head>

<body>
    <div class="game-container"></div>
    <template id="log-template">
        <div class="log-header">
            <div class="log-header-title">
                游戏日志
            </div>
            <div class="log-icon-close">
                <img src="./assets/monopoly-life/arrow.png" />
            </div>
        </div>
        <div class="log-body"></div>
        <style>
            :host {
                position: absolute;
                left: 0;
                bottom: 0;
                width: 400px;
                background-color: rgb(66, 136, 167);
                box-shadow: inset 0px -3px 0 rgb(4, 70, 98);
                border-radius: 0 4px 0 0;
                transition: top .5s linear;
            }

            :host(.close) {
                bottom: calc(-30vh + 40px);
            }

            :host(.close) .log-icon-close {
                transform: rotate(180deg);
                top: 5px;
            }

            .log-header-title {
                line-height: 40px;
                font-size: 18px;
                font-weight: bold;
                text-align: center;
                color: rgb(252, 255, 254);
                text-shadow: 0px 2px 0 rgb(78, 75, 67);
            }

            .log-body {
                margin: 0 10px 10px;
                height: calc(30vh - 50px);
                overflow-y: auto;
                background-color: rgb(233, 227, 220);
                border-radius: 4px;
            }

            .log-icon-close {
                position: absolute;
                top: 10px;
                right: 20px;
            }

            .log-item-table {
                border-top: 1px solid #000;
                border-left: 1px solid #000;
                border-collapse: collapse;
            }

            .log-item-table td {
                border-right: 1px solid #000;
                border-bottom: 1px solid #000;
                text-align: center;
            }
        </style>
    </template>
    <template id="player-template">
        <style>
            :host {
                display: flex;
                position: absolute;
                top: 0px;
                right: 0;
                background-color: rgb(66, 136, 167);
                box-shadow: inset 0px -3px 0 rgb(4, 70, 98);
                border-radius: 0 4px 0 0;
                padding: 10px;
                transition: top .5s linear;
            }

            :host(.close) {
                transform: 0;
            }

            .player-item {
                background-color: rgb(233, 227, 220);
                border-radius: 4px;
                display: flex;
                padding: 10px 10px 10px 10px;
                width: 228px;
                align-items: center;
                box-sizing: border-box;
                position: relative;
            }

            .player-item+.player-item {
                margin-left: 10px;
            }

            .player-item.active .player-avatar::after {
                position: absolute;
                background-image: url('./assets/monopoly-life/dice.png');
                background-size: 20px 20px;
                background-repeat: no-repeat;
                width: 20px;
                height: 20px;
                right: 0;
                bottom: 0;
                content: "";
            }

            .player-item:hover .player-more {
                display: block;
            }

            .player-more {
                display: none;
                position: absolute;
                top: 100%;
                left: 0;
                right: 0;
                background-color: rgb(66, 136, 167);
                box-shadow: inset 0px -3px 0 rgb(4, 70, 98);
                border-radius: 0 4px 0 0;
                padding: 10px;
            }

            .player-target-container,
            .player-exp-container,
            .player-cre-container {
                display: flex;
                flex-direction: column;
                padding: 5px 10px 0;
                background-color: rgb(233, 227, 220);
                border-radius: 4px;
            }

            .player-exp-container,
            .player-cre-container {
                margin-top: 10px;
            }

            .player-target-body,
            .player-exp-body,
            .player-cre-body {
                display: flex;
                padding: 5px 0px
            }

            .player-exp-body .player-icon:not(.none),
            .player-cre-body .player-icon:not(.none) {
                border-radius: 4px;
                border: 1px solid rgb(66, 136, 167);
                color: rgb(66, 136, 167);
                padding: 0 4px;
            }

            .player-target-item {
                display: flex;
                flex: 1;
                justify-content: center;
            }

            .player-avatar {
                margin-right: 10px;
                width: 60px;
                height: 60px;
                background-size: contain;
                border-radius: 50%;
                position: relative;
            }

            .player-base-info {
                display: flex;
                flex-direction: column;
                flex: 1;
            }

            .player-base-info>div {
                display: flex;
                align-items: center;
                padding-left: 4px;
                background-color: rgb(207, 199, 190);
                border-radius: 4px;
                flex: 1;
                margin-top: 2px;
            }

            .player-repute,
            .player-happy,
            .player-chance,
            .player-experience {
                display: flex;
                flex: 1;
            }


            .player-icon {
                display: flex;
                align-items: center;
                margin-right: 5px;
            }
        </style>
    </template>
    <template id="player-template-player-item-internal">
        <div class="player-item">
            <div class="player-avatar"></div>
            <div class="player-base-info">
                <div class="player-riches">
                    <div class="player-icon">
                        <img src="./assets/monopoly-life/riches.png" />
                    </div>
                    <div class="player-value">0</div>
                </div>
                <div class="player-salary">
                    <div class="player-icon">
                        <img src="./assets/monopoly-life/salary.png" />
                    </div>
                    <div class="player-value">0</div>
                </div>
                <div>
                    <div class="player-repute">
                        <div class="player-icon">
                            <img src="./assets/monopoly-life/repute.png" />
                        </div>
                        <div class="player-value">0</div>
                    </div>
                    <div class="player-happy">
                        <div class="player-icon">
                            <img src="./assets/monopoly-life/happy.png" />
                        </div>
                        <div class="player-value">0</div>
                    </div>
                </div>
                <div>
                    <div class="player-chance">
                        <div class="player-icon">
                            <img src="./assets/monopoly-life/chance.png" width="20px" />
                        </div>
                        <div class="player-value">0</div>
                    </div>
                    <div class="player-experience">
                        <div class="player-icon">
                            <img src="./assets/monopoly-life/experience.png" width="20px" />
                        </div>
                        <div class="player-value">0</div>
                    </div>
                </div>
            </div>

            <div class="player-more">
                <div class="player-target-container">
                    <div class="player-target-title">
                        目标值
                    </div>
                    <div class="player-target-body">
                        <div class="player-target-item target-riches">
                            <div class="player-icon">
                                <img src="./assets/monopoly-life/riches.png" />
                            </div>
                            <div class="player-value">60000</div>
                        </div>
                        <div class="player-target-item target-repute">
                            <div class="player-icon">
                                <img src="./assets/monopoly-life/repute.png" />
                            </div>
                            <div class="player-value">0</div>
                        </div>
                        <div class="player-target-item target-happy">
                            <div class="player-icon">
                                <img src="./assets/monopoly-life/happy.png" />
                            </div>
                            <div class="player-value">0</div>
                        </div>
                    </div>
                </div>
                <div class="player-exp-container">
                    <div class="player-exp-title">
                        职业经验
                    </div>
                    <div class="player-exp-body">
                        <div class="player-icon none">
                            无
                        </div>
                    </div>
                </div>
                <div class="player-cre-container">
                    <div class="player-cre-title">
                        大学进修
                    </div>
                    <div class="player-cre-body">
                        <div class="player-icon none">
                            无
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </template>
    <template id="dice-template">
        <div class="dice-header">
            <div class="dice-header-title">
                掷骰子
            </div>
        </div>
        <div class="dice-body">
            <div class="dice-item-container">
                <img class="dice-item" src="./assets/monopoly/1.png" />
                <img class="dice-item" src="./assets/monopoly/1.png" />
            </div>
            <div class="game-btn-confirm">开始转动</div>
        </div>
        <style>
            :host {
                position: absolute;
                left: 50%;
                top: 50%;
                transform: translate(-50%, -50%);
                width: 300px;
                background-color: rgb(66, 136, 167);
                box-shadow: inset 0px -3px 0 rgb(4, 70, 98);
                border-radius: 4px;
                transition: top .5s linear;
            }

            :host(.close) {
                top: -100%;
            }

            .dice-header-title {
                line-height: 40px;
                font-size: 18px;
                font-weight: bold;
                text-align: center;
                color: rgb(252, 255, 254);
                text-shadow: 0px 2px 0 rgb(78, 75, 67);
            }

            .dice-body {
                margin: 0 10px 13px;
                overflow-y: auto;
                display: flex;
                flex-direction: column;
                align-items: center;
                background-color: rgb(233, 227, 220);
                border-radius: 4px;
            }

            .dice-item {
                width: 50px;
                height: 50px;
                background-repeat: no-repeat;
                background-size: 100%;
            }

            .dice-item:nth-child(n+2) {
                margin-left: 20px;
            }

            .dice-item-container {
                display: flex;
                margin-top: 10px;
                margin-bottom: 10px;
            }

            .dice-item.hidden {
                display: none;
            }

            .game-btn-confirm {
                margin-bottom: 10px;
            }
        </style>
    </template>
    <template id="hospital-template">
        <div class="hospital-header">
            <div class="hospital-header-title">
                医院
            </div>
        </div>
        <div class="hospital-content">
            <div class="hospital-message">
                不可使用机会卡或经验卡离开此格，需掷骰决定如何离开医院：
            </div>
            <div class="hospital-list">
                <div class="hospital-item">
                    <img src="./assets/monopoly/1.png" />
                    <img src="./assets/monopoly/2.png" />
                    再休养一回合
                </div>
                <div class="hospital-item">
                    <img src="./assets/monopoly/3.png" />
                    <img src="./assets/monopoly/4.png" />
                    <img src="./assets/monopoly/5.png" />
                    <img src="./assets/monopoly/6.png" />
                    付薪级一半的医药费给有医学学位的人下回合出院
                </div>
                <div class="hospital-item">
                    (若无人有医学学位则给银行)
                </div>
            </div>
            <div class="hospital-footer">
                <div class="hospital-dice">
                    <img src="./assets/monopoly/5.png" />
                </div>
                <div class="game-btn-confirm">开始转动</div>
            </div>
        </div>
        <style>
            :host {
                position: absolute;
                left: 50%;
                top: 50%;
                transform: translate(-50%, -50%);
                width: 600px;
                transition: top .5s linear;
                background-color: rgb(66, 136, 167);
                box-shadow: inset 0px -3px 0 rgb(4, 70, 98);
                border-radius: 4px;
            }

            :host(.close) {
                top: -100%;
            }

            .hospital-header-title {
                line-height: 40px;
                font-size: 18px;
                font-weight: bold;
                text-align: center;
                color: rgb(252, 255, 254);
                text-shadow: 0px 2px 0 rgb(78, 75, 67);
            }

            .hospital-message {
                font-size: 16px;
                margin-bottom: 10px;
            }

            .hospital-content {
                margin: 0 10px 13px;
                padding: 10px;
                background-color: rgb(233, 227, 220);
                border-radius: 4px;
            }

            .hospital-item {
                display: flex;
                align-items: center;
                margin-bottom: 10px;
            }

            .hospital-item img {
                height: 30px
            }

            .hospital-item img:last-child {
                margin-right: 10px;
            }

            .hospital-item:last-child {
                color: #a1a1a1;
                font-size: 14px;
            }

            .hospital-footer {
                display: flex;
                justify-content: space-between;
                background-color: rgb(207, 199, 190);
                padding: 10px;
                border-radius: 4px;
            }

            .hospital-dice {
                display: flex;
                align-items: center;
            }
        </style>
    </template>
    <template id="park-template">
        <div class="park-header">
            <div class="park-header-title">
                公园板凳
            </div>
        </div>
        <div class="park-content">
            <div class="park-message">
                不可使用机会卡或经验卡离开此格，投掷一骰，若点数为双数，下回合即可离开：
            </div>
            <div class="park-list">
                <div class="park-item">
                    <img src="./assets/monopoly/2.png" />
                    <img src="./assets/monopoly/4.png" />
                    <img src="./assets/monopoly/6.png" />
                    下回合离开
                </div>
                <div class="park-item">
                    <img src="./assets/monopoly/1.png" />
                    <img src="./assets/monopoly/3.png" />
                    <img src="./assets/monopoly/5.png" />
                    下回合继续
                </div>
            </div>
            <div class="park-footer">
                <div class="park-dice">
                    <img src="./assets/monopoly/5.png" />
                </div>
                <div class="game-btn-confirm">开始转动</div>
            </div>
        </div>
        <style>
            :host {
                position: absolute;
                left: 50%;
                top: 50%;
                transform: translate(-50%, -50%);
                width: 500px;
                padding: 0 10px 13px;
                background-color: rgb(66, 136, 167);
                box-shadow: inset 0px -3px 0 rgb(4, 70, 98);
                border-radius: 4px;
            }

            :host(.close) {
                top: -100%;
            }

            .park-header-title {
                line-height: 40px;
                font-size: 18px;
                font-weight: bold;
                text-align: center;
                color: rgb(252, 255, 254);
                text-shadow: 0px 2px 0 rgb(78, 75, 67);
            }

            .park-content {
                background-color: rgb(233, 227, 220);
                border-radius: 4px;
                padding: 10px;
            }

            .park-message {
                font-size: 16px;
                margin-bottom: 10px;
            }

            .park-item {
                display: flex;
                align-items: center;
                margin-bottom: 10px;
            }

            .park-item img {
                height: 30px
            }

            .park-item img:last-child {
                margin-right: 10px;
            }

            .park-footer {
                display: flex;
                justify-content: space-between;
                background-color: rgb(207, 199, 190);
                padding: 10px;
                border-radius: 4px;
            }

            .park-dice {
                display: flex;
                align-items: center;
            }
        </style>
    </template>
    <template id="role-template">
        <div class="role-header">
            <div class="role-header-title">
                角色选择
            </div>
        </div>
        <div class="role-body">
            <div class="role-item-container"></div>
            <div class="target-item-container"></div>
            <div class="target-hint">
                输入财富、名誉、快乐的预定目标，财富1000元为1分，三项目标加起来必须刚好60分。
            </div>
            <div class="game-btn-confirm">确认选择</div>
        </div>
        <style>
            :host {
                position: absolute;
                left: 50%;
                top: 50%;
                transform: translate(-50%, -50%);
                width: 600px;
                background-color: rgb(66, 136, 167);
                box-shadow: inset 0px -3px 0 rgb(4, 70, 98);
                border-radius: 0 4px 0 0;
                transition: top .5s linear;
            }

            :host(.close) {
                top: -100%;
            }

            .role-header-title {
                line-height: 40px;
                font-size: 18px;
                font-weight: bold;
                text-align: center;
                color: rgb(252, 255, 254);
                text-shadow: 0px 2px 0 rgb(78, 75, 67);
            }

            .role-body {
                margin: 0 10px 13px;
                overflow-y: auto;
                display: flex;
                flex-direction: column;
                align-items: center;
                overflow-x: hidden;
                background-color: rgb(233, 227, 220);
                border-radius: 4px;
                padding: 10px;
            }

            .role-item-container {
                display: flex;
                width: 100%;
                justify-content: space-between;
            }

            .role-item {
                display: flex;
                flex: 1;
                justify-content: center;
                align-items: center;
                flex-direction: column;
                padding: 20px;
                margin: 20px 10px;
            }

            .role-item.active {
                background-color: #2776ec2f;
            }

            .role-item-name {
                margin-top: 10px;
                display: flex;
                justify-content: center;
            }

            .role-item-avatar {
                height: 60px;
                width: 60px;
                border-radius: 50%;
                background-size: contain;
            }

            .target-item-container {
                display: flex;
                width: 100%;
                padding-bottom: 20px;
            }

            .target-item {
                display: flex;
                flex-direction: column;
                flex: 1;
                align-items: center;
            }

            .target-item input {
                width: 70px;
                outline: none;
                border-top: 0;
                border-left: 0;
                border-right: 0;
                border-width: 1px;
                border-color: #2e2e2e;
                background-color: transparent;
                margin-left: 5px;
                text-align: center;
            }

            .target-item.error input {
                border-color: #cc0000;
            }

            .target-item.valid input {
                border-color: #00cc00;
            }


            .target-item-content {
                display: flex;
                align-items: center;
                margin-bottom: 5px;
            }

            .target-hint {
                width: 100%;
                font-size: 14px;
                margin-bottom: 20px;
                margin-left: 20px;
            }
        </style>
    </template>
    <template id="lottery-template">
        <div class="lottery-header">
            <div class="lottery-header-title">
                购买彩票
            </div>
        </div>
        <div class="lottery-content">
            <div class="lottery-message">
                每人可轮流选择是否执行以下动作：掷一颗骰并根据掷出点数失去或获得金钱
            </div>
            <div class="lottery-list">
                <div class="lottery-item">
                    <div style="width:200px">
                        <img src="./assets/monopoly/1.png" />
                        <img src="./assets/monopoly/2.png" />
                        <img src="./assets/monopoly/3.png" />
                        <img src="./assets/monopoly/4.png" />
                    </div>
                    <div class="flex-row-center" style="color:red">
                        <img src="./assets/monopoly-life/riches.png" />-1000元
                    </div>
                </div>
                <div class="lottery-item">
                    <div style="width:200px">
                        <img src="./assets/monopoly/5.png" />
                        <img src="./assets/monopoly/6.png" />
                    </div>
                    <div class="flex-row-center" style="color:green">
                        <img src="./assets/monopoly-life/riches.png" />+5000元
                    </div>
                </div>
            </div>
            <div class="lottery-player-list">
            </div>
        </div>
        <style>
            :host {
                position: absolute;
                left: 50%;
                top: 50%;
                transform: translate(-50%, -50%);
                width: 600px;
                padding: 0px 10px 13px;
                transition: top .5s linear;
                background-color: rgb(66, 136, 167);
                box-shadow: inset 0px -3px 0 rgb(4, 70, 98);
                border-radius: 4px;
            }

            :host(.close) {
                top: -100%;
            }

            .lottery-header-title {
                line-height: 40px;
                font-size: 18px;
                font-weight: bold;
                text-align: center;
                color: rgb(252, 255, 254);
                text-shadow: 0px 2px 0 rgb(78, 75, 67);
            }

            .lottery-content {
                background-color: rgb(233, 227, 220);
                border-radius: 4px;
                padding: 10px;
            }

            .lottery-message {
                font-size: 16px;
                margin-bottom: 10px;
            }

            .lottery-item {
                display: flex;
                align-items: center;
                margin-bottom: 10px;
            }

            .lottery-item img {
                height: 30px
            }

            .lottery-item img:last-child {
                margin-right: 10px;
            }

            .lottery-footer {
                display: flex;
                justify-content: space-between;
                margin-top: 20px;
                margin-bottom: 20px;
            }

            .player-item {
                display: flex;
                align-items: center;
                box-sizing: border-box;
                position: relative;
            }

            .player-item .player-avatar {
                width: 40px;
                height: 40px;
                background-size: contain;
                border-radius: 50%;
                margin-right: 20px;
            }


            .player-operate {
                display: flex;
                justify-content: space-between;
                align-items: center;
                margin-top: 10px;
                background-color: rgb(207, 199, 190);
                padding: 10px;
                border-radius: 4px;
            }

            .operate-item .game-btn-confirm:last-child {
                background-color: #a1a1a1;
                margin-left: 10px;
            }

            .operate-item {
                display: flex;
                align-items: center;
            }

            .operate-dice {
                margin-right: 20px;
                display: flex;
                align-items: center;
            }
        </style>
    </template>
    <template id="lottery-template-player-operate-internal">
        <div class="player-operate">
            <div class="player-item">
                <div class="player-avatar">
                </div>
                <div class="player-name"></div>
            </div>
            <div class="operate-item">
                <div class="operate-dice">
                    <img src="./assets/monopoly/5.png" />
                </div>
                <div class="game-btn-confirm">购买</div>
                <div class="game-btn-confirm">放弃</div>
            </div>
        </div>
    </template>
    <template id="stock-template">
        <div class="stock-header">
            <div class="stock-header-title">
                股票市场
            </div>
        </div>

        <!-- <div class="logo">
            <img src="./assets/monopoly-life/stock.png" />
        </div> -->
        <div class="stock-content">
            <div class="stock-message">
                每人可轮流选择是否执行以下动作：
            </div>
            <div class="stock-list">
                <span style="color: red;">-2000元 </span>并且<span style="color: green;">+🎲×1000元</span>
            </div>
            <div class="stock-player-list">
            </div>
        </div>
        <style>
            :host {
                position: absolute;
                left: 50%;
                top: 50%;
                transform: translate(-50%, -50%);
                width: 600px;
                padding-left: 10px;
                padding-right: 10px;
                padding-bottom: 10px;
                transition: top .5s linear;
                background-color: rgb(66, 136, 167);
                box-shadow: inset 0px -3px 0 rgb(4, 70, 98);
                border-radius: 4px;
            }

            :host(.close) {
                top: -100%;
            }

            .stock-header-title {
                line-height: 40px;
                font-size: 18px;
                font-weight: bold;
                text-align: center;
                color: rgb(252, 255, 254);
                text-shadow: 0px 2px 0 rgb(78, 75, 67);
            }

            .stock-content {
                background-color: rgb(233, 227, 220);
                border-radius: 4px;
                padding: 10px;
            }


            .stock-message {
                font-size: 16px;
                margin-bottom: 10px;
            }

            .stock-list {
                margin-bottom: 20px;
            }

            .stock-footer {
                display: flex;
                justify-content: space-between;
                margin-top: 20px;
                margin-bottom: 20px;
            }

            .player-item {
                display: flex;
                align-items: center;
                box-sizing: border-box;
                position: relative;
            }

            .player-item .player-avatar {
                width: 40px;
                height: 40px;
                background-size: contain;
                border-radius: 50%;
                margin-right: 20px;
            }


            .player-operate {
                display: flex;
                justify-content: space-between;
                align-items: center;
                margin-top: 10px;
                background-color: rgb(207, 199, 190);
                padding: 10px;
                border-radius: 4px;
            }

            .operate-item .game-btn-confirm:last-child {
                margin-left: 10px;
            }

            .operate-item {
                display: flex;
                align-items: center;
            }

            .operate-dice {
                margin-right: 20px;
                display: flex;
                align-items: center;
            }
        </style>
    </template>
    <template id="stock-template-player-operate-internal">
        <div class="player-operate">
            <div class="player-item">
                <div class="player-avatar">
                </div>
                <div class="player-name"></div>
            </div>
            <div class="operate-item">
                <div class="operate-dice">
                    <img src="./assets/monopoly/5.png" />
                </div>
                <div class="game-btn-confirm">购买</div>
                <div class="game-btn-confirm">放弃</div>
            </div>
        </div>
    </template>
    <template id="mall-template">
        <div class="mall-header">
            <div class="mall-header-title">
                商场
            </div>
        </div>
        <div class="mall-content">
            <div class="mall-message">
                大肆采购所有人都要执行：
            </div>
            <div class="mall-list">
                <span style="color: red;">-🎲×300元</span>
            </div>
            <div class="mall-player-list">
            </div>
        </div>
        <style>
            :host {
                position: absolute;
                left: 50%;
                top: 50%;
                transform: translate(-50%, -50%);
                width: 600px;
                padding-left: 10px;
                padding-right: 10px;
                padding-bottom: 10px;
                transition: top .5s linear;
                background-color: rgb(66, 136, 167);
                box-shadow: inset 0px -3px 0 rgb(4, 70, 98);
                border-radius: 4px;
            }

            :host(.close) {
                top: -100%;
            }

            .mall-header-title {
                line-height: 40px;
                font-size: 18px;
                font-weight: bold;
                text-align: center;
                color: rgb(252, 255, 254);
                text-shadow: 0px 2px 0 rgb(78, 75, 67);
            }

            .mall-content {
                background-color: rgb(233, 227, 220);
                border-radius: 4px;
                padding: 10px;
            }


            .mall-message {
                font-size: 16px;
                margin-bottom: 10px;
            }

            .mall-list {
                margin-bottom: 20px;
            }

            .mall-footer {
                display: flex;
                justify-content: space-between;
                margin-top: 20px;
                margin-bottom: 20px;
            }

            .player-item {
                display: flex;
                align-items: center;
                box-sizing: border-box;
                position: relative;
            }

            .player-item .player-avatar {
                width: 40px;
                height: 40px;
                background-size: contain;
                border-radius: 50%;
                margin-right: 20px;
            }


            .player-operate {
                display: flex;
                justify-content: space-between;
                align-items: center;
                margin-top: 10px;
                background-color: rgb(207, 199, 190);
                padding: 10px;
                border-radius: 4px;
            }

            .operate-item {
                display: flex;
                align-items: center;
            }

            .operate-dice {
                margin-right: 20px;
                display: flex;
                align-items: center;
            }
        </style>
    </template>
    <template id="mall-template-player-operate-internal">
        <div class="player-operate">
            <div class="player-item">
                <div class="player-avatar">
                </div>
                <div class="player-name"></div>
            </div>
            <div class="operate-item">
                <div class="operate-dice">
                    <img src="./assets/monopoly/5.png" />
                </div>
                <div class="game-btn-confirm">投掷</div>
            </div>
        </div>
    </template>
    <template id="gallery-template">
        <div class="gallery-header">
            <div class="gallery-header-title">
                画廊
            </div>
        </div>
        <div class="gallery-content">
            <div class="gallery-message">
                每人可轮流选择是否执行以下动作：
            </div>
            <div class="gallery-list">
                <div class="flex-row-center">
                    <div style="color: red;">-3000元</div>并且
                    <div class="flex-row-center" style="color: green;">+🎲×<img
                            src="./assets/monopoly-life/happy.png" /></div>
                </div>
            </div>
            <div class="gallery-player-list">
            </div>
        </div>
        <style>
            :host {
                position: absolute;
                left: 50%;
                top: 50%;
                transform: translate(-50%, -50%);
                width: 600px;
                padding-left: 10px;
                padding-right: 10px;
                padding-bottom: 10px;
                transition: top .5s linear;
                background-color: rgb(66, 136, 167);
                box-shadow: inset 0px -3px 0 rgb(4, 70, 98);
                border-radius: 4px;
            }

            :host(.close) {
                top: -100%;
            }

            .gallery-header-title {
                line-height: 40px;
                font-size: 18px;
                font-weight: bold;
                text-align: center;
                color: rgb(252, 255, 254);
                text-shadow: 0px 2px 0 rgb(78, 75, 67);
            }

            .gallery-content {
                background-color: rgb(233, 227, 220);
                border-radius: 4px;
                padding: 10px;
            }


            .gallery-message {
                font-size: 16px;
                margin-bottom: 10px;
            }

            .gallery-list {
                margin-bottom: 20px;
            }

            .gallery-footer {
                display: flex;
                justify-content: space-between;
                margin-top: 20px;
                margin-bottom: 20px;
            }

            .player-item {
                display: flex;
                align-items: center;
                box-sizing: border-box;
                position: relative;
            }

            .player-item .player-avatar {
                width: 40px;
                height: 40px;
                background-size: contain;
                border-radius: 50%;
                margin-right: 20px;
            }


            .player-operate {
                display: flex;
                justify-content: space-between;
                align-items: center;
                margin-top: 10px;
                background-color: rgb(207, 199, 190);
                padding: 10px;
                border-radius: 4px;
            }

            .operate-item .game-btn-confirm:last-child {
                margin-left: 10px;
            }

            .operate-item {
                display: flex;
                align-items: center;
            }

            .operate-dice {
                margin-right: 20px;
                display: flex;
                align-items: center;
            }
        </style>
    </template>
    <template id="gallery-template-player-operate-internal">
        <div class="player-operate">
            <div class="player-item">
                <div class="player-avatar">
                </div>
                <div class="player-name"></div>
            </div>
            <div class="operate-item">
                <div class="operate-dice">
                    <img src="./assets/monopoly/5.png" />
                </div>
                <div class="game-btn-confirm">投掷</div>
                <div class="game-btn-confirm">放弃</div>
            </div>
        </div>
    </template>
    <template id="wishing-template">
        <div class="wishing-header">
            <div class="wishing-header-title">
                许愿井
            </div>
        </div>
        <div class="wishing-content">
            <div class="wishing-message">
                每人可轮流选择是否执行以下动作：
            </div>
            <div class="wishing-message">
                付出左边列举的一项，获得右边列举的一项
            </div>
            <div class="wishing-player-list">
            </div>
        </div>
        <style>
            :host {
                position: absolute;
                left: 50%;
                top: 50%;
                transform: translate(-50%, -50%);
                /* width: 600px; */
                padding-left: 10px;
                padding-right: 10px;
                padding-bottom: 10px;
                transition: top .5s linear;
                background-color: rgb(66, 136, 167);
                box-shadow: inset 0px -3px 0 rgb(4, 70, 98);
                border-radius: 4px;
            }

            :host(.close) {
                top: -100%;
            }

            .wishing-header-title {
                line-height: 40px;
                font-size: 18px;
                font-weight: bold;
                text-align: center;
                color: rgb(252, 255, 254);
                text-shadow: 0px 2px 0 rgb(78, 75, 67);
            }

            .wishing-content {
                background-color: rgb(233, 227, 220);
                border-radius: 4px;
                padding: 10px;
            }


            .wishing-message {
                font-size: 16px;
                margin-bottom: 10px;
            }

            .wishing-list {
                margin-bottom: 20px;
            }

            .wishing-footer {
                display: flex;
                justify-content: space-between;
                margin-top: 20px;
                margin-bottom: 20px;
            }

            .wishing-player-list {
                display: flex;
            }

            .player-item {
                display: flex;
                flex-direction: column;
                align-items: center;
                box-sizing: border-box;
                position: relative;
            }

            .player-item .player-avatar {
                width: 40px;
                height: 40px;
                background-size: contain;
                border-radius: 50%;
            }


            .player-operate {
                display: flex;
                flex-direction: column;
                justify-content: space-between;
                align-items: center;
                margin-top: 10px;
                background-color: rgb(207, 199, 190);
                padding: 10px;
                border-radius: 4px;
                margin-right: 10px;
            }

            .player-operate:last-child {
                margin-right: 0;
            }

            .operate-item .game-btn-confirm:last-child {
                margin-left: 10px;
            }

            .operate-item {
                display: flex;
                align-items: center;
            }

            .operate-dice {
                margin-right: 20px;
                display: flex;
                align-items: center;
            }

            .select-list {
                display: flex;
                flex-wrap: wrap;
                justify-content: space-between;
                width: 150px;
                margin: 10px 0;
            }

            .select-item {
                display: flex;
                width: calc(50% - 5px);
                background-color: rgb(233, 227, 220);
                justify-content: center;
                align-items: center;
                padding: 3px 0;
                margin-top: 4px;
            }

            .select-item img {
                width: 20px;
                height: 20px;
            }

            .select-item.active {
                background-color: rgb(232, 139, 26);
            }
        </style>
    </template>
    <template id="wishing-template-player-operate-internal">
        <div class="player-operate">
            <div class="player-item">
                <div class="player-avatar"></div>
                <div class="player-name"></div>
            </div>

            <div class="select-list">
                <div class="select-item left active" data-happy="-5">
                    -5<img src="./assets/monopoly-life/happy.png" />
                </div>
                <div class="select-item right active" data-happy="4">
                    +4<img src="./assets/monopoly-life/happy.png" />
                </div>
                <div class="select-item left" data-repute="-5">
                    -5<img src="./assets/monopoly-life/repute.png" />
                </div>
                <div class="select-item right" data-repute="4">
                    +4<img src="./assets/monopoly-life/repute.png" />
                </div>
                <div class="select-item left" data-riches="-5000">
                    -5000元
                </div>
                <div class="select-item right" data-riches="4000">
                    +4000元
                </div>
            </div>
            <div class="operate-item">
                <div class="game-btn-confirm">许愿</div>
                <div class="game-btn-confirm">放弃</div>
            </div>
        </div>
    </template>
    <template id="dialog-template">
        <div class="dialog-header">
            <div class="dialog-header-title">
                对话框
            </div>
        </div>
        <div class="dialog-body">
            <div class="dialog-body-content">
                请选择你想要的选项
            </div>
            <div class="dialog-body-options">
                <div class="dialog-body-option">
                    A
                </div>
                <div class="dialog-body-option">
                    B
                </div>
                <div class="dialog-body-option">
                    C
                </div>
            </div>
        </div>

        <style>
            :host {
                position: absolute;
                left: 50%;
                top: 50%;
                transform: translate(-50%, -50%);
                background-color: rgb(66, 136, 167);
                box-shadow: inset 0px -3px 0 rgb(4, 70, 98);
                border-radius: 4px;
                width: 500px;
                transition: top .5s linear;
            }

            :host(.close) {
                top: -100%;
            }


            .dialog-header-title {
                line-height: 40px;
                font-size: 18px;
                font-weight: bold;
                text-align: center;
                color: rgb(252, 255, 254);
                text-shadow: 0px 2px 0 rgb(78, 75, 67);
            }

            .dialog-body {
                margin: 0 10px 13px;
                overflow-y: auto;
                display: flex;
                flex-direction: column;
                overflow-x: hidden;

                background-color: rgb(233, 227, 220);
                border-radius: 4px;
                padding: 10px;
            }

            .dialog-body-option {
                margin-top: 10px;
                display: flex;
                align-items: center;
                background-color: rgb(207, 199, 190);
                padding: 10px;
                border-radius: 4px;
                cursor: pointer;
                justify-content: center;
            }

            .dialog-body-content img {
                width: 100%;
            }

            .rank {
                display: flex;
                flex-direction: column;
                padding: 10px;
            }

            .rank .rank-item {
                display: flex;
                align-items: center;
                margin-bottom: 10px;
            }

            .rank .rank-item-icon {
                position: relative;
            }

            .rank .player-avatar {
                width: 60px;
                height: 60px;
                border-radius: 50%;
            }

            .rank .rank-icon {
                width: 40px;
                height: 40px;
                position: absolute;
                left: -15px;
                top: -15px;
            }

            .rank .rank-item-content {
                flex: 1;
                padding-left: 10px;
            }

            .rank .rank-item-completion {
                font-weight: bold;
                padding: 0 10px;
            }

            .rank .player-name {
                font-weight: bold;
            }

            .rank .rank-item.hidden {
                display: none;
            }
        </style>
    </template>
    <template id="dialog-template-rank-internal">
        <div class="rank">
            <div class="rank-item">
                <div class="rank-item-icon">
                    <img src="./assets/monopoly/black.jpg" class="player-avatar">
                    <img src="./assets/monopoly-life/rank1.svg" class="rank-icon">
                </div>
                <div class="rank-item-content">
                    <div class="player-name">小黑</div>
                </div>
                <div class="rank-item-completion">
                    目标完成度: 100%
                </div>
            </div>
            <div class="rank-item">
                <div class="rank-item-icon">
                    <img src="./assets/monopoly/black.jpg" class="player-avatar">
                    <img src="./assets/monopoly-life/rank2.svg" class="rank-icon">
                </div>
                <div class="rank-item-content">
                    <div class="player-name">小黑</div>
                </div>
                <div class="rank-item-completion">
                    100%
                </div>
            </div>
            <div class="rank-item">
                <div class="rank-item-icon">
                    <img src="./assets/monopoly/black.jpg" class="player-avatar">
                    <img src="./assets/monopoly-life/rank3.svg" class="rank-icon">
                </div>
                <div class="rank-item-content">
                    <div class="player-name">小黑</div>
                </div>
                <div class="rank-item-completion">
                    100%
                </div>
            </div>
            <div class="rank-item">
                <div class="rank-item-icon">
                    <img src="./assets/monopoly/black.jpg" class="player-avatar">
                </div>
                <div class="rank-item-content">
                    <div class="player-name">小黑</div>
                </div>
                <div class="rank-item-completion">
                    100%
                </div>
            </div>
        </div>
    </template>
    <template id="chance-template">
        <div class="chance-header">
            <div class="chance-header-title">
                机会
            </div>
        </div>
        <div class="chance-body">
            <div class="chance-body-content">
                恭喜获得以下奖励
            </div>
            <div class="chance-body-card-list">
                <div class="chance-body-card">
                    <img src="./assets/monopoly-life/chance.png">
                    <div>xxxxx</div>
                </div>
                <div class="chance-body-card">
                    <img src="./assets/monopoly-life/chance.png">
                    <div>xxxxx</div>
                </div>
            </div>
            <div class="chance-footer">
                <div class="game-btn-confirm">关闭</div>
            </div>
        </div>

        <style>
            :host {
                position: absolute;
                left: 50%;
                top: 50%;
                transform: translate(-50%, -50%);
                background-color: rgb(66, 136, 167);
                box-shadow: inset 0px -3px 0 rgb(4, 70, 98);
                border-radius: 4px;
                width: 500px;
            }

            :host(.close) {
                top: -100%;
            }


            .chance-header-title {
                line-height: 40px;
                font-size: 18px;
                font-weight: bold;
                text-align: center;
                color: rgb(252, 255, 254);
                text-shadow: 0px 2px 0 rgb(78, 75, 67);
            }

            .chance-body {
                margin: 0 10px 13px;
                overflow-y: auto;
                display: flex;
                flex-direction: column;
                overflow-x: hidden;

                background-color: rgb(233, 227, 220);
                border-radius: 4px;
                padding: 10px;
            }

            .chance-body-content img {
                width: 100%;
            }

            .chance-body-card {
                display: flex;
                align-items: center;
                background-color: rgb(207, 199, 190);
                padding: 10px;
                border-radius: 4px;
                width: calc(100% - 20px);
                margin-top: 10px;
            }

            .chance-body-card>img {
                width: 40px;
                margin-right: 10px;
            }

            .chance-footer {
                margin-top: 10px;
                display: flex;
                justify-content: center;
            }
        </style>
    </template>
    <template id="card-holder-template">
        <div class="card-holder-header">
            <div class="card-holder-header-title">
                卡包
            </div>
        </div>
        <div class="card-holder-body">
            <div class="card-holder-body-content">
                选择一张卡片使用
            </div>
            <div class="card-holder-body-card-list">
                <div class="card-holder-body-card">
                    <img src="./assets/monopoly-life/chance.png">
                    <div>xxxxx</div>
                    <div class="game-btn-confirm">使用</div>
                </div>
            </div>
            <div class="card-holder-footer">
                <div class="game-btn-confirm">关闭</div>
            </div>
        </div>

        <style>
            :host {
                position: absolute;
                left: 50%;
                top: 50%;
                transform: translate(-50%, -50%);
                background-color: rgb(66, 136, 167);
                box-shadow: inset 0px -3px 0 rgb(4, 70, 98);
                border-radius: 4px;
                width: 500px;
            }

            :host(.close) {
                top: -100%;
            }


            .card-holder-header-title {
                line-height: 40px;
                font-size: 18px;
                font-weight: bold;
                text-align: center;
                color: rgb(252, 255, 254);
                text-shadow: 0px 2px 0 rgb(78, 75, 67);
            }

            .card-holder-body {
                margin: 0 10px 13px;
                overflow-y: auto;
                display: flex;
                flex-direction: column;
                overflow-x: hidden;

                background-color: rgb(233, 227, 220);
                border-radius: 4px;
                padding: 10px;
            }

            .card-holder-body-card-list {
                max-height: 400px;
                overflow-y: auto;
            }

            .card-holder-body-content img {
                width: 100%;
            }

            .card-holder-body-card {
                display: flex;
                align-items: center;
                background-color: rgb(207, 199, 190);
                padding: 10px;
                border-radius: 4px;
                width: calc(100% - 20px);
                margin-top: 10px;
            }

            .card-holder-body-card>img {
                width: 40px;
                margin-right: 10px;
            }

            .card-holder-body-card>div:not(.game-btn-confirm) {
                flex: 1;
                margin-right: 10px;
            }

            .card-holder-footer {
                margin-top: 10px;
                display: flex;
                justify-content: center;
            }
        </style>
    </template>
    <template id="travel-mode-template">
        <div class="travel-mode-header">
            <div class="travel-mode-header-title">
                出行方式
            </div>
        </div>
        <div class="travel-mode-content">
            <div class="travel-mode-message">
                请选择你的方式：
            </div>

            <div class="travel-mode-btn-list">
                <div class="travel-mode-btn">
                    <div class="travel-mode-btn-icon">
                        <img src="./assets/monopoly-life/dice.png">
                    </div>
                    <div class="travel-mode-btn-text">掷骰子</div>
                </div>


                <div class="travel-mode-btn">
                    <div class="travel-mode-btn-icon">
                        <img src="./assets/monopoly-life/chance.png">
                        <img src="./assets/monopoly-life/experience.png">
                    </div>
                    <div class="travel-mode-btn-text">道具卡</div>
                </div>
            </div>

        </div>
        <style>
            :host {
                position: absolute;
                left: 50%;
                top: 50%;
                transform: translate(-50%, -50%);
                /* width: 600px; */
                padding-left: 10px;
                padding-right: 10px;
                padding-bottom: 10px;
                transition: top .5s linear;
                background-color: rgb(66, 136, 167);
                box-shadow: inset 0px -3px 0 rgb(4, 70, 98);
                border-radius: 4px;
            }

            :host(.close) {
                top: -100%;
            }

            .travel-mode-header-title {
                line-height: 40px;
                font-size: 18px;
                font-weight: bold;
                text-align: center;
                color: rgb(252, 255, 254);
                text-shadow: 0px 2px 0 rgb(78, 75, 67);
            }

            .travel-mode-content {
                background-color: rgb(233, 227, 220);
                border-radius: 4px;
                padding: 10px;
            }

            .travel-mode-btn-list {
                display: flex;
            }

            .travel-mode-btn {
                padding: 20px;
                margin: 10px;
                display: flex;
                align-items: center;
                flex-direction: column;
                background-color: rgb(207, 199, 190);
            }

            .travel-mode-btn:active {
                opacity: .7;
            }

            .travel-mode-btn:first-child .travel-mode-btn-icon {
                text-align: center;
            }

            .travel-mode-btn-icon {
                width: 60px;
                height: 50px;
                position: relative;
            }


            .travel-mode-btn-icon img {
                width: 50px;
            }

            .travel-mode-btn-icon img+img {
                position: absolute;
                width: 50px;
                bottom: 0;
                left: 10px;
            }
        </style>
    </template>
    <template id="map-template">
        <div class="map-container"></div>
        <style>
            :host {
                position: absolute;
                left: 0;
                top: 0;
                background-size: contain;
            }

            :host(.anim) {
                transition: all .2s linear;
            }

            .station-item {
                position: absolute;
                border: 2px solid #00cc00;
                display: flex;
                justify-content: center;
                align-items: center;
            }

            .player-item {
                display: inline-flex;
            }

            .player-avatar {
                margin: 10px;
                width: 60px;
                height: 60px;
                background-size: contain;
                border-radius: 50%;
            }

            .player-item.active .player-avatar {
                border-width: 5px;
                border-style: solid;
            }
        </style>
    </template>
    <script type="module">
        import { cloneDeep } from "https://gcore.jsdelivr.net/npm/@3r/tool/lib/common.js";
        import { Maths } from "https://gcore.jsdelivr.net/npm/@3r/tool/lib/maths.js";
        import { Randoms } from "https://gcore.jsdelivr.net/npm/@3r/tool/lib/randoms.js";

        // 增加移除方法
        Array.prototype.remove = function (obj) {
            let removeIndex = this.findIndex(o => o == obj)
            return this.splice(removeIndex, 1)
        }

        /** 获取数字
         * @param {string} str 
         */
        function getNumber(str) {
            return +str.match(/[-\d]{0,}/)[0]
        }

        /** 加载图
         * @param {string} src
         * @returns {Promise<HTMLImageElement>}
         */
        function loadImage(src) {
            return new Promise((resolve) => {
                let image = new Image()
                image.src = src;
                image.onload = (ev) => {
                    resolve(image)
                }
            })
        }

        /** 拖拽元素
         * @param {HTMLImageElement} dom
         * @param {{minX:number,maxX:number,minY:number,maxY:number}} options
         */
        function dragging(dom, options) {
            // 开始坐标位置
            let startX = 0;
            let startY = 0;
            let startLeft = 0;
            let startTop = 0;
            let isMove = false;

            dom.classList.add('anim')

            dom.addEventListener("mousedown", ev => {
                // 移除动画标签
                dom.classList.remove('anim')

                startX = ev.x;
                startY = ev.y;
                startLeft = getNumber(dom.style.left);
                startTop = getNumber(dom.style.top);
                isMove = true;
            })

            dom.addEventListener("mousemove", ev => {
                if (!isMove) return;
                let left = startLeft + ev.x - startX;
                let top = startTop + ev.y - startY;
                left = Maths.inRange(left, options.minX, options.maxX)
                top = Maths.inRange(top, options.minY, options.maxY)
                dom.style.left = left + 'px';
                dom.style.top = top + 'px';
            })

            dom.addEventListener("mouseup", ev => {
                // 增加动画标签
                dom.classList.add('anim')
                isMove = false;
            })

            let screenWidth = document.body.clientWidth
            let screenHeight = document.body.clientHeight

            /**
             * @param {HTMLDivElement} target
             */
            const lookAt = async function (target) {
                let left = - (target.offsetLeft + target.offsetWidth / 2 - screenWidth / 2)
                let top = - (target.offsetTop + target.offsetHeight / 2 - screenHeight / 2)
                left = Maths.inRange(left, options.minX, options.maxX)
                top = Maths.inRange(top, options.minY, options.maxY)
                dom.style.left = left + 'px';
                dom.style.top = top + 'px';
            }

            return {
                lookAt
            }
        }

        /** 等待多久 
         */
        function wait(ms) {
            return new Promise((resolve) => {
                setTimeout(resolve, ms)
            })
        }

        /** 模板加载
         * @param {string} selectors 
         * @param {boolean} isInternal 是否是内部模板
         */
        function templateLoad(selectors, isInternal = false) {
            let template = document.querySelector(selectors)
            /** @type {HTMLDivElement}*/
            let clone = document.importNode(template.content, true);
            if (isInternal) return clone
            let container = document.createElement('div')
            container.id = Randoms.uuid();
            let shadowRoot = container.attachShadow({ mode: "open" });
            shadowRoot.appendChild(clone)
            // 加载全局样式
            const globalStyleSheets = new CSSStyleSheet()
            for (let i = 0; i < document.styleSheets.length; i++) {
                const styleSheet = document.styleSheets.item(i);
                const rules = styleSheet.rules
                for (let j = 0; j < rules.length; j++) {
                    const rule = rules.item(j);
                    globalStyleSheets.insertRule(rule.cssText)
                }
            }
            shadowRoot.adoptedStyleSheets = [globalStyleSheets]
            // 仅输出宿主节点
            // 影子节点可以通过 container.shadowRoot.querySelector 进行查询
            return container
        }

        /**
         * 观察变量
         */
        function observeVariable(func) {
            let variable = {}
            let variableProxy = new Proxy(variable, {
                get(variable, prop) {
                    return variable[prop]
                },
                async set(variable, prop, value) {
                    variable[prop] = value;
                    await func?.(variable, prop, value)
                },
            })
            return variableProxy
        }


        /** 玩家行动美剧 */
        const PlayerActionEnum = {
            /** 默认 */
            NONE: 0,
            /** 再次 */
            AGAIN: 1,
            /** 暂停 */
            PAUSE: -1
        }

        /** 卡片类型 */
        const ChanceCardType = {
            /** 机会 */
            CHANCE: 1,
            /** 经验 */
            EXPERIENCE: 2,
        }

        /** 证书类型 */
        const CredentialType = {
            /** 普通 */
            NORMAL: 1,
            /** 医学 */
            MEDICINE: 2,
            /** 法律 */
            LAW: 3,
            /** 工程 */
            ENGINEERING: 4,
            /** 科学 */
            SCIENCE: 5
        }

        /** 位置类型枚举 */
        const PositionTypeEnum = {
            /** 环道 */
            LOOP: 1,
            /** 农业 */
            AGRICULTURE: 2,
            /** 大学 */
            UNIVERSITY: 3,
            /** 企业 */
            ENTERPRISE: 4,
            /** 航行 */
            VOYAGE: 5,
            /** 公益 */
            WELFARE: 6,
            /** 影视 */
            MOVIES: 7,
            /** 采矿 */
            MINING: 8,
            /** 太空 */
            SPACE: 9
        }

        /** 经验类型 */
        const ExperienceType = {
            /** 农业 */
            AGRICULTURE: 2,
            /** 企业 */
            ENTERPRISE: 4,
            /** 航行 */
            VOYAGE: 5,
            /** 公益 */
            WELFARE: 6,
            /** 影视 */
            MOVIES: 7,
            /** 采矿 */
            MINING: 8,
            /** 太空 */
            SPACE: 9
        }
        /** 证书对应经验 */
        const CredentialToExperience = {
            [CredentialType.NORMAL]: ExperienceType.ENTERPRISE,
            [CredentialType.SCIENCE]: ExperienceType.SPACE,
            [CredentialType.LAW]: ExperienceType.WELFARE,
            [CredentialType.ENGINEERING]: ExperienceType.MINING,
        }

        /** 图片配置 */
        const ICON_CONFIG = {
            RICHES: `<img src="./assets/monopoly-life/riches.png" />`,
            SALARY: `<img src="./assets/monopoly-life/salary.png" />`,
            REPUTE: `<img src="./assets/monopoly-life/repute.png" />`,
            HAPPY: `<img src="./assets/monopoly-life/happy.png" /> `,
            ARROW: `<img src="./assets/monopoly-life/arrow.png" />`,
            EXPERIENCE: `<img src="./assets/monopoly-life/experience.png"/>`,
            CHANCE: `<img src="./assets/monopoly-life/chance.png"/>`,
        }

        class Credential {
            name = ""
            type = CredentialType.NORMAL
            /**
             * @param {Credential} cre
             */
            constructor(cre) {
                for (const key in cre) {
                    this[key] = cre[key]
                }
            }
        }

        class Experience {
            name = ""
            type = ExperienceType.AGRICULTURE
            /**
             * @param {Experience} exp
             */
            constructor(exp) {
                for (const key in exp) {
                    this[key] = exp[key]
                }
            }
        }

        class Role {
            id = Randoms.uuid()
            name = ''
            avatar = ''
            color = ''
            targetRiches = 0
            targetRepute = 0;
            targetHapply = 0
            /**
             * @param {Role} role
             */
            constructor(role) {
                for (const key in role) {
                    this[key] = role[key]
                }
            }
        }

        class Player extends Role {
            /** 财富 */
            riches = 5000
            /** 薪水 */
            salary = 2000
            /** 声望 */
            repute = 0;
            /** 幸福 */
            happy = 0;

            /** 位置 */
            position = 0;
            /** 位置类型 */
            positionType = PositionTypeEnum.LOOP
            /** 历史记录 */
            // history = []
            /** 行动状态 */
            actionState = PlayerActionEnum.NONE
            /** 游戏胜利 */
            win = false

            /** @type {ChanceCard[]} */
            cardHolder = []
            /** @type {Credential[]} */
            credentials = []
            /** @type {Experience[]} */
            experiences = []
            /**
             * @param {Player} player
             */
            constructor(player) {
                super(player)
                for (const key in player) {
                    this[key] = player[key]
                }
            }
        }

        class PlayerChanged {
            /** 财富 */
            riches = 0
            /** 薪水 */
            salary = 0
            /** 声望 */
            repute = 0
            /** 幸福 */
            happy = 0
            /** 行动状态 */
            actionState = PlayerActionEnum.NONE
        }

        class ChanceCard {
            id = '';
            name = ''
            description = ''
            passive = false;
            type = ChanceCardType.CHANCE;
            useDice = false;
            /** 免疫 */
            immunity = PositionTypeEnum.AGRICULTURE
            /** @param {Player} player */
            /** @param {Player[]} rival */
            use = (player, rival) => { }
            /**
             * @param {ChanceCard} card
             */
            constructor(card) {
                for (const key in card) {
                    this[key] = card[key]
                }
            }
        }

        class SceneStation {
            /** @type {Player} */
            curPlayer = null
            /** @type {Player[]} */
            allPlayers = []
            dicePoints = 0
        }

        class Station {
            /** @type {string} */
            id = ""
            /** @type {string} */
            left = ""
            /** @type {string} */
            top = ""
            /** @type {string} */
            width = ""
            /** @type {string} */
            height = ""
            /** @type {string} */
            name = ""
            /** @type {string} */
            description = ""
            /** @type {HTMLDivElement} */
            dom = null
            /**
             * 经过
             * @param {SceneStation} scene
             */
            via‌ = (scene) => { }
            /**
             * 停止
             * @param {SceneStation} scene
             */
            stop = (scene) => { }
            /**
             * 离开
             * @param {SceneStation} scene
             */
            leave
            /**
             * @param {Station} station
             */
            constructor(station) {
                for (const key in station) {
                    this[key] = station[key]
                }
            }
        }




        /** 日志控件 左下角
         * @author 	 linyisonger
         * @date 	 2024-11-29
         */
        const logContainer = function () {
            const fragment = templateLoad('#log-template')
            const closeDom = fragment.shadowRoot.querySelector('.log-icon-close')
            const bodyDom = fragment.shadowRoot.querySelector('.log-body')
            closeDom.addEventListener('click', () => fragment.classList.toggle('close'))
            /** 窗口日志打印
             * @param {string} str
             */
            const log = (str) => {
                bodyDom.innerHTML += `<div class="log-item">${str}</div>`
                bodyDom.scrollTop = Number.MAX_SAFE_INTEGER;
            }
            return {
                fragment,
                log
            }
        }();

        /** 玩家控件 右上角
         * @author 	 linyisonger
         * @date 	 2024-11-29
         */
        const playerContainer = function () {
            const fragment = templateLoad("#player-template")
            fragment.classList.add('close');

            /**
             * 检查完成度 
             * @param {Player} player
             */
            let checkCompletion = player => {
                let richeCompletion = player.riches / 1000 / (player.targetRiches || 1)
                let happyCompletion = player.happy / (player.targetHapply || 1);
                let reputeCompletion = player.repute / (player.targetRepute || 1)

                richeCompletion = Math.min(1, richeCompletion)
                happyCompletion = Math.min(1, happyCompletion)
                reputeCompletion = Math.min(1, reputeCompletion)

                return {
                    player,
                    completion: richeCompletion * happyCompletion * reputeCompletion,
                    richeCompletion,
                    happyCompletion,
                    reputeCompletion
                }
            }


            /**
             * 检查玩家破产
             * @param {Player} player
             */
            let checkBankruptcy = async player => {
                if (player.riches < 0) {
                    player.riches = 0;
                    upd([player])
                    logContainer.log(`玩家[${player.name}]破产了，移动到公园`)
                    await dialogContainer.open({
                        title: '破产',
                        content: `<img src="./assets/monopoly-life/bankruptcy.jpeg"/>`,
                        options: ['关闭']
                    })
                    await mapContainer.flash(player, "LOOP", 24)
                }
            }


            /**
             * 检查玩家是否胜利
             * @param {Player[]} allPlayers
             */
            let checkWin = async allPlayers => {
                let completionList = allPlayers.map(p => checkCompletion(p))
                // 完成度大于一 游戏结束
                if (completionList.some(c => c.completion == 1)) {
                    completionList.sort((a, b) => b.completion - a.completion)

                    const firstPlayer = completionList[0].player
                    logContainer.log(`玩家[${firstPlayer.name}]率先达成目标，游戏结束`)
                    let rankDom = templateLoad('#dialog-template-rank-internal', true).querySelector(".rank")
                    let rankItemList = rankDom.querySelectorAll('.rank-item')

                    rankItemList.forEach((rankItem, index) => {
                        let completion = completionList[index]
                        if (completion) {
                            let playerAvatarDom = rankItem.querySelector('.player-avatar')
                            let playerNameDom = rankItem.querySelector('.player-name')
                            let completionDom = rankItem.querySelector('.rank-item-completion')
                            playerAvatarDom.setAttribute('src', completion.player.avatar)
                            playerNameDom.textContent = completion.player.name
                            completionDom.textContent = `目标完成度：${(completion.completion * 100).toFixed(2)}%`
                        }
                        else rankItem.classList.add('hidden')
                    })
                    let selected = await dialogContainer.open({
                        title: '排行榜',
                        content: rankDom.outerHTML,
                        options: ["重新开始"]
                    })

                    // 重新开始刷新页面
                    if (selected == 0) {
                        location.reload()
                    }
                }
            }


            /**
             * 玩家加入
             * @param {Player[]} players
             */
            let join = async (players) => {
                for (const player of players) {
                    let playerItemDom = templateLoad('#player-template-player-item-internal', true).querySelector('.player-item')
                    playerItemDom.setAttribute('data-id', player.id)
                    playerItemDom.querySelector('.player-avatar').setAttribute('style', `background-image:url(${player.avatar})`)
                    playerItemDom.querySelector('.player-riches .player-value').textContent = player.riches
                    playerItemDom.querySelector('.player-salary .player-value').textContent = player.salary
                    playerItemDom.querySelector('.player-repute .player-value').textContent = player.repute
                    playerItemDom.querySelector('.player-happy .player-value').textContent = player.happy
                    playerItemDom.querySelector('.target-riches .player-value').textContent = player.targetRiches * 1000
                    playerItemDom.querySelector('.target-repute .player-value').textContent = player.targetRepute
                    playerItemDom.querySelector('.target-happy .player-value').textContent = player.targetHapply
                    fragment.shadowRoot.appendChild(playerItemDom)
                }
                fragment.classList.remove('close')
            }

            /**
             * 玩家更新
             * @param {Player[]} players
             */
            let upd = async (players) => {

                for (let i = 0; i < players.length; i++) {
                    const player = players[i];
                    let dom = fragment.shadowRoot.querySelector(`.player-item[data-id="${player.id}"]`)
                    dom.querySelector('.player-riches .player-value').textContent = player.riches
                    dom.querySelector('.player-salary .player-value').textContent = player.salary
                    dom.querySelector('.player-repute .player-value').textContent = player.repute
                    dom.querySelector('.player-happy .player-value').textContent = player.happy
                    dom.querySelector('.player-chance .player-value').textContent = player.cardHolder.filter(c => c.type === ChanceCardType.CHANCE).length
                    dom.querySelector('.player-experience .player-value').textContent = player.cardHolder.filter(c => c.type === ChanceCardType.EXPERIENCE).length


                    let playerCreBodyDom = dom.querySelector('.player-cre-body')
                    let playerExpBodyDom = dom.querySelector('.player-exp-body')

                    playerCreBodyDom.innerHTML = ``;
                    player.credentials.forEach(c => {
                        playerCreBodyDom.innerHTML += `<div class="player-icon" title="${c.name}">${c.name[0]}</div>`
                    })
                    if (!playerCreBodyDom.innerHTML) playerCreBodyDom.innerHTML = `<div class="player-icon none">无</div>`

                    playerExpBodyDom.innerHTML = ``;
                    console.log(player.experiences);
                    player.experiences.forEach(c => {
                        playerExpBodyDom.innerHTML += `<div class="player-icon" title="${c.name}">${c.name[0]}</div>`
                    })
                    if (!playerExpBodyDom.innerHTML) playerExpBodyDom.innerHTML = `<div class="player-icon none">无</div>`

                }
            }

            /**
             * 下一个玩家
             * @param {Player} player
             */
            let next = async (player) => {
                let lastDom = fragment.shadowRoot.querySelector('.player-item.active');
                let currDom = fragment.shadowRoot.querySelector(`.player-item[data-id="${player.id}"]`);
                if (lastDom) lastDom.classList.remove('active')
                currDom.classList.add('active')
            }

            return {
                fragment,
                join,
                upd,
                next,
                checkBankruptcy,
                checkWin
            }
        }();

        /** 骰子控件 弹窗
         * @author 	 linyisonger
         * @date 	 2024-11-29
         */
        const diceContainer = await async function () {
            const fragment = templateLoad('#dice-template')
            fragment.classList.add('close')

            const diceImages = await Promise.all([
                './assets/monopoly/1.png',
                './assets/monopoly/2.png',
                './assets/monopoly/3.png',
                './assets/monopoly/4.png',
                './assets/monopoly/5.png',
                './assets/monopoly/6.png',
            ].map(u => loadImage(u)))
            /** @type {HTMLDivElement} */
            const diceItemContainerDom = fragment.shadowRoot.querySelector('.dice-item-container')
            /** @type {HTMLDivElement} */
            const confirmDom = fragment.shadowRoot.querySelector('.game-btn-confirm')
            /**
             * @param {HTMLImageElement} dom
             */
            const roll = async (dom) => {
                let result = 0;
                for (let i = 0; i < 15; i++) {
                    await wait(100);
                    const val = Randoms.int(0, 6)
                    dom.setAttribute('src', `${diceImages[val].src}`)
                    result = val + 1
                }
                return result;
            }
            /** 
             * @param {number} count
             * @return {Promise<number>}
             */
            const open = (count) => {
                return new Promise((resolve) => {
                    for (let i = 0; i < diceItemContainerDom.children.length; i++) {
                        const dom = diceItemContainerDom.children.item(i)
                        dom.classList.add('hidden')
                    }
                    for (let i = 0; i < count; i++) {
                        /** @type {HTMLDivElement} */
                        const dom = diceItemContainerDom.children.item(i)
                        dom.classList.remove('hidden')
                        dom.setAttribute('src', `${diceImages[0].src}`)
                    }
                    fragment.classList.remove('close')

                    const confirmFunc = async function () {
                        // 执行🎲
                        const diceItemDomList = diceItemContainerDom.querySelectorAll('.dice-item:not(.hidden)')
                        // 循环15次约等于1.5s
                        const allRoll = []
                        // 循环加入
                        diceItemDomList.forEach(item => allRoll.push(roll(item)))
                        const total = Maths.sum(await Promise.all(allRoll))
                        await wait(500)
                        fragment.classList.add('close')
                        resolve(total)
                    }
                    confirmDom.removeEventListener("click", confirmFunc)
                    confirmDom.addEventListener("click", confirmFunc)
                })
            }
            return {
                fragment,
                open,
                roll
            }
        }()


        /**
         * 角色控件 弹窗
         * @author 	 linyisonger
         * @date 	 2024-11-29
         */
        const roleContainer = await async function () {
            const roleList = [
                new Role({
                    name: '小黑',
                    avatar: './assets/monopoly/black.jpg',
                    color: 'black'
                }),
                new Role({
                    name: '小红',
                    avatar: './assets/monopoly/red.jpg',
                    color: 'red'
                }),
                new Role({
                    name: '小黄',
                    avatar: './assets/monopoly/yellow.jpg',
                    color: 'yellow'
                }),
                new Role({
                    name: '小蓝',
                    avatar: './assets/monopoly/blue.jpg',
                    color: 'blue'
                })
            ]
            let roleItemHtml = ``
            let targetItemHtml = ``
            for (let i = 0; i < roleList.length; i++) {
                const role = roleList[i];
                roleItemHtml += `
                    <div class="role-item active">
                        <div class="role-item-avatar" style="background-image:url(${role.avatar})"></div>
                        <div class="role-item-name">${role.name}</div>
                    </div> 
                `
                targetItemHtml += `
                    <div class="target-item">
                        <div class="target-item-content">
                            ${ICON_CONFIG.RICHES}
                            <input type="number" placeholder="财富" max="60" min="0" step="1"/>
                        </div>
                        <div class="target-item-content">
                            ${ICON_CONFIG.REPUTE}  
                            <input type="number" placeholder="名誉" max="60" min="0" step="1"/>
                        </div>
                        <div class="target-item-content">
                            ${ICON_CONFIG.HAPPY}
                            <input type="number" placeholder="快乐" max="60" min="0" step="1"/>
                        </div>
                    </div>
                `
            }
            const fragment = templateLoad("#role-template")
            fragment.classList.add('close')
            const roleItemContainerDom = fragment.shadowRoot.querySelector('.role-item-container')
            const targetItemContainerDom = fragment.shadowRoot.querySelector('.target-item-container')
            roleItemContainerDom.innerHTML = roleItemHtml
            targetItemContainerDom.innerHTML = targetItemHtml
            roleItemContainerDom.querySelectorAll('.role-item').forEach((dom) => {
                dom.addEventListener('click', function () {
                    if (dom.classList.contains('active')) {
                        if (roleItemContainerDom.querySelectorAll('.role-item.active').length < 3) return
                        dom.classList.remove('active')
                    }
                    else {
                        dom.classList.add('active')
                    }
                })
            })
            /** @type {HTMLDivElement} */
            const confirmDom = fragment.shadowRoot.querySelector('.game-btn-confirm')
            /**
             * @return {Promise<roles[]>}
             */
            const open = () => {
                fragment.classList.remove('close')
                logContainer.log('请选择角色填写各自的目标值')
                return new Promise((resolve) => {
                    confirmDom.addEventListener("click", () => {
                        let selected = []
                        let roles = roleItemContainerDom.querySelectorAll('.role-item')
                        let targets = targetItemContainerDom.querySelectorAll('.target-item')
                        let valid = true
                        roles.forEach((role, roleIndex) => {
                            let target = targets.item(roleIndex)
                            target.classList.remove('error')
                            target.classList.remove('valid')
                            if (role.classList.contains('active')) {
                                let inputs = target.querySelectorAll('input')
                                let targetRiches = +inputs.item(0).value
                                let targetRepute = +inputs.item(1).value
                                let targetHapply = +inputs.item(2).value
                                selected.push(new Role({
                                    ...roleList[roleIndex],
                                    targetRiches,
                                    targetRepute,
                                    targetHapply
                                }))
                                if (targetRiches + targetRepute + targetHapply != 60) {
                                    target.classList.add('error')
                                    valid = false;
                                }
                                else {
                                    target.classList.add('valid')
                                    valid = true
                                }
                            }
                        })

                        if (valid) {
                            fragment.classList.add('close')
                            resolve(selected);
                        }
                    })

                })
            }
            return {
                fragment,
                open
            }
        }()


        /**
         * 战斗控件
         * @author 	 linyisonger
         * @date 	 2024-11-29
         */
        const fightContainer = await async function () {
            const open = () => {
                // p1 vs p2

                // 掷骰子

                // 结果
            }

            return {
                open
            }
        }()


        /**
         * 获取机会[经验]卡控件
         * @author 	 linyisonger
         * @date 	 2024-11-29
         */
        const chanceContainer = await async function () {
            const fragment = templateLoad('#chance-template')
            fragment.classList.add('close')
            /**
             * @type {ChanceCard[]}
             */
            let changeList = [
                {
                    id: "1",
                    name: "航海",
                    description: '不掷骰，直接移动到航海入口。若符合条件，下回合可进入职业道路。',
                    use: async (player) => await mapContainer.flash(player, "LOOP", 14)
                },
                {
                    id: "2",
                    name: "大学进修",
                    description: '不掷骰，直接移动到大学入口。若符合条件，下回合可进入职业道路。',
                    use: async (player) => await mapContainer.flash(player, "LOOP", 7)
                },
                {
                    id: '3',
                    name: '电影拍摄',
                    description: '不掷骰，直接移动到电影入口。若符合条件，下回合可进入职业道路。',
                    use: async (player) => await mapContainer.flash(player, "LOOP", 22)
                },
                {
                    id: '4',
                    name: '三亚度假',
                    description: '不掷骰，直接移动到三亚度假。',
                    use: async (player) => await mapContainer.flash(player, "LOOP", 16)
                },
                {
                    id: '5',
                    name: '尖端科技',
                    description: '在太空探险职业道路中，若遇到任何损失效果，展示此卡就算无事。（可重复使用）',
                    passive: true,
                    immunity: PositionTypeEnum.SPACE
                },
                {
                    id: '6',
                    name: '采矿开发',
                    description: '不掷骰，直接移动到采矿入口。若符合条件，下回合可进入职业道路。',
                    use: async (player) => await mapContainer.flash(player, "LOOP", 27)

                },
                {
                    id: '7',
                    name: '操作熟练',
                    description: '在采矿开发职业道路中，若遇到任何损失效果，展示此卡就算无事。（可重复使用）',
                    passive: true,
                    immunity: PositionTypeEnum.MINING

                },
                {
                    id: '8',
                    name: '超级人才',
                    description: '不掷骰，直接移动到任何职业入口。若符合条件，下回合可进入职业道路。',
                    use: async (player) => {
                        let options = [
                            {
                                name: '航海',
                                func: async () => await mapContainer.flash(player, "LOOP", 14)
                            },
                            {
                                name: '采矿开发',
                                func: async () => await mapContainer.flash(player, "LOOP", 27)
                            },
                            {
                                name: '电影拍摄',
                                func: async () => await mapContainer.flash(player, "LOOP", 22)
                            },
                            {
                                name: '大学进修',
                                func: async () => await mapContainer.flash(player, "LOOP", 7)
                            },
                            {
                                name: '公益事业',
                                func: async () => await mapContainer.flash(player, "LOOP", 19)
                            },
                            {
                                name: '农业开垦',
                                func: async () => await mapContainer.flash(player, "LOOP", 4)
                            },
                            {
                                name: '太空探险',
                                func: async () => await mapContainer.flash(player, "LOOP", 30)
                            },
                            {
                                name: '企业经营',
                                func: async () => await mapContainer.flash(player, "LOOP", 11)
                            }
                        ]

                        let selected = await dialogContainer.open({
                            title: '转换跑道',
                            content: '选择一个玩家进行抽取',
                            options: options.map(a => a.name)
                        })
                        await options[selected].func()
                    }
                },
                {
                    id: '9',
                    name: '公益事业',
                    description: '不掷骰，直接移动到公益入口。若符合条件，下回合可进入职业道路。',
                    use: async (player) => await mapContainer.flash(player, "LOOP", 19)
                },
                {
                    id: '10',
                    name: '农业开垦',
                    description: '不掷骰，直接移动到农业入口。若符合条件，下回合可进入职业道路。',
                    use: async (player) => await mapContainer.flash(player, "LOOP", 4)
                },
                {
                    id: '11',
                    name: '太空探险',
                    description: '不掷骰，直接移动到太空入口。若符合条件，下回合可进入职业道路。',
                    use: async (player) => await mapContainer.flash(player, "LOOP", 30)
                },
                {
                    id: '12',
                    name: '转换跑道',
                    description: '从别人手上抽走一张机会卡或经验卡，然后掷骰前进',
                    useDice: true,
                    /**
                     * @param {Player} player
                     * @param {Player[]} rival
                     */
                    use: async (player, rival) => {

                        const hasCardRival = rival.filter(a => a.cardHolder.length)
                        const options = hasCardRival.map(r => r.name)
                        options.push('取消')

                        let selected = await dialogContainer.open({
                            title: '转换跑道',
                            content: '选择一个玩家进行抽取',
                            options
                        })

                        let selectedOption = options[selected]

                        if (selectedOption === '取消') {
                            logContainer.log(`玩家[${player.name}]取消使用道具卡`)
                        }
                        else {
                            logContainer.log(`玩家[${player.name}]使用道具卡`)
                            let rivalItem = hasCardRival.find(r => r.name === selectedOption)
                            let randomCardIndex = Randoms.int(0, rivalItem.cardHolder.length)
                            let card = rivalItem.cardHolder.splice(randomCardIndex, 1)[0]
                            player.cardHolder.push(card)
                            playerContainer.upd([player, rivalItem])
                            logContainer.log(`玩家[${rivalItem.name}]失去道具卡[${card.name}]`)
                            logContainer.log(`玩家[${player.name}]得到道具卡[${card.name}]`)
                        }
                    }
                },
                {
                    id: '13',
                    name: '企业经营',
                    description: '不掷骰，直接移动到企业入口。若符合条件，下回合可进入职业道路。',
                    use: async (player) => await mapContainer.flash(player, "LOOP", 11)
                },
                {
                    id: '14',
                    name: '手腕高明',
                    description: '在电影拍摄职业道路中，若遇到任何损失效果，展示此卡就算无事。（可重复使用）',
                    passive: true,
                    immunity: PositionTypeEnum.MOVIES
                },
                {
                    id: '15',
                    name: '眼光精准',
                    description: '在企业经营职业道路中，若遇到任何损失效果，展示此卡就算无事。（可重复使用）',
                    passive: true,
                    immunity: PositionTypeEnum.ENTERPRISE
                },
            ].map(c => new ChanceCard(c))

            let experienceList = [
                {
                    id: '16',
                    description: '不掷骰，向前移动一格。',
                    use: async (player) => await mapContainer.move(player, 1)
                },
                {
                    id: '17',
                    description: '不掷骰，向前移动一格。',
                    use: async (player) => await mapContainer.move(player, 1)
                },
                {
                    id: '18',
                    description: '不掷骰，向前移动一格。',
                    use: async (player) => await mapContainer.move(player, 1)
                },
                {
                    id: '19',
                    description: '不掷骰，向前移动二格。',
                    use: async (player) => await mapContainer.move(player, 2)
                },
                {
                    id: '20',
                    description: '不掷骰，向前移动二格。',
                    use: async (player) => await mapContainer.move(player, 2)

                },
                {
                    id: '21',
                    description: '不掷骰，向前移动二格。',
                    use: async (player) => await mapContainer.move(player, 2)
                },
                {
                    id: '22',
                    description: '不掷骰，向前移动三格。',
                    use: async (player) => await mapContainer.move(player, 3)
                },
                {
                    id: '23',
                    description: '不掷骰，向前移动三格。',
                    use: async (player) => await mapContainer.move(player, 3)
                },
                {
                    id: '24',
                    description: '不掷骰，向前移动三格。',
                    use: async (player) => await mapContainer.move(player, 3)
                }
            ].map(c => new ChanceCard({ ...c, type: ChanceCardType.EXPERIENCE }))

            /**
             * 打开获取
             * @param {Player} player
             * @param {string} type
             * @param {number} count
             */
            const open = async (player, type, count) => {
                fragment.classList.remove('close')
                return new Promise((resolve) => {
                    const titleDom = fragment.shadowRoot.querySelector('.chance-header-title')
                    const cardList = cloneDeep(type == ChanceCardType.CHANCE ? changeList : experienceList)
                    const cardIcon = type == ChanceCardType.CHANCE ? ICON_CONFIG.CHANCE : ICON_CONFIG.EXPERIENCE
                    titleDom.textContent = type == ChanceCardType.CHANCE ? '机会' : '经验'
                    const cardListDom = fragment.shadowRoot.querySelector(".chance-body-card-list")
                    const closeDom = fragment.shadowRoot.querySelector(".game-btn-confirm")
                    cardListDom.innerHTML = ''
                    const resultList = Randoms.confusion(cardList).slice(0, count)

                    // 游戏日志

                    player.cardHolder.push(...resultList)
                    playerContainer.upd([player])

                    resultList.forEach((card) => {
                        const cardDom = document.createElement('div')
                        const cardTxtDom = document.createElement('div')
                        cardDom.innerHTML = cardIcon
                        cardTxtDom.textContent = card.description;
                        cardDom.classList.add('chance-body-card')
                        cardDom.appendChild(cardTxtDom)
                        cardListDom.appendChild(cardDom)
                    })
                    const closeFunc = () => {
                        fragment.classList.add('close')
                        resolve(resultList)
                    }
                    closeDom.removeEventListener('click', closeFunc)
                    closeDom.addEventListener('click', closeFunc)
                })
            }

            return {
                fragment,
                open
            }
        }()


        /**
         * 卡包控件
         * @author 	 linyisonger
         * @date 	 2024-11-29
         */
        const cardHolderContainer = await async function () {
            const fragment = templateLoad('#card-holder-template')
            fragment.classList.add('close')
            /**
             * 打开获取
             * @param {Player} player
             * @param {Player[]} rival 对手
             * @returns {Promise<{useDice:boolean}>}
             */
            const open = function (player, rival) {
                return new Promise((resolve) => {
                    fragment.classList.remove('close')
                    let cardListDom = fragment.shadowRoot.querySelector('.card-holder-body-card-list')
                    let closeDom = fragment.shadowRoot.querySelector('.card-holder-footer .game-btn-confirm')
                    cardListDom.innerHTML = ''
                    player.cardHolder.forEach(function (card) {
                        let cardDom = document.createElement('div')
                        let textDom = document.createElement('div')
                        cardDom.classList.add('card-holder-body-card')
                        textDom.textContent = card.description
                        cardDom.innerHTML = card.type == ChanceCardType.CHANCE ? ICON_CONFIG.CHANCE : ICON_CONFIG.EXPERIENCE
                        cardDom.appendChild(textDom)
                        cardListDom.appendChild(cardDom)

                        if (!card.passive) {
                            let btnDom = document.createElement('div')
                            btnDom.classList.add('game-btn-confirm')
                            btnDom.textContent = '使用'
                            btnDom.addEventListener('click', async () => {
                                fragment.classList.add('close')
                                await card?.use(player, rival)
                                player.cardHolder.remove(card)
                                resolve({ useDice: card.useDice })
                            })
                            cardDom.appendChild(btnDom)
                        }
                    })

                    let closeFunc = () => {
                        fragment.classList.add('close')
                        resolve({ useDice: true })
                    }
                    closeDom.removeEventListener('click', closeFunc)
                    closeDom.addEventListener('click', closeFunc)
                })
            }

            return {
                fragment,
                open
            }
        }()



        /**
         * 医院控件
         * @author 	 linyisonger
         * @date 	 2024-12-12
         */
        const hospitalContainer = await async function () {
            const fragment = templateLoad('#hospital-template')
            fragment.classList.add('close')
            const confirmDom = fragment.shadowRoot.querySelector('.game-btn-confirm')
            /**
             * @return {Promise<number>}
             */
            const open = function () {
                fragment.classList.remove('close')
                return new Promise((resolve) => {
                    const confirmFunc = async function () {
                        // 执行🎲
                        /** @type {HTMLImageElement} */
                        const diceItemDom = fragment.shadowRoot.querySelector('.hospital-dice img')
                        // 结果
                        let total = await diceContainer.roll(diceItemDom)
                        await wait(500)
                        fragment.classList.add('close')
                        resolve(total)
                    }
                    confirmDom.removeEventListener("click", confirmFunc)
                    confirmDom.addEventListener("click", confirmFunc)
                })
            }

            return {
                fragment,
                open
            }
        }()

        /**
         * 公园控件
         * @author 	 linyisonger
         * @date 	 2024-12-12
         */
        const parkContainer = await async function () {
            const fragment = templateLoad("#park-template")
            fragment.classList.add('close')
            const confirmDom = fragment.shadowRoot.querySelector('.game-btn-confirm')
            /**
             * @return {Promise<number>}
             */
            const open = function () {
                fragment.classList.remove('close')
                return new Promise((resolve) => {
                    const confirmFunc = async function () {
                        // 执行🎲
                        /** @type {HTMLImageElement} */
                        const diceItemDom = fragment.shadowRoot.querySelector('.park-dice img')
                        // 结果
                        let total = await diceContainer.roll(diceItemDom)
                        await wait(500)
                        fragment.classList.add('close')
                        resolve(total)
                    }
                    confirmDom.removeEventListener("click", confirmFunc)
                    confirmDom.addEventListener("click", confirmFunc)
                })
            }

            return {
                fragment,
                open
            }
        }()


        /**
         * 彩票店控件
         * @author 	 linyisonger
         * @date 	 2024-12-16
         */
        const lotteryContainer = await async function () {
            const fragment = templateLoad("#lottery-template")
            fragment.classList.add('close')
            const confirmDom = fragment.shadowRoot.querySelector('.game-btn-confirm')
            const lotteryPlayerListDom = fragment.shadowRoot.querySelector('.lottery-player-list')

            /**
             * @param {Player[]} players
             * @return {Promise<number>}
             */
            const open = function (players) {
                fragment.classList.remove('close')
                return new Promise((resolve) => {
                    let resultProxy = observeVariable(async (result) => {
                        if (Object.keys(result).length === players.length) {
                            await wait(500);
                            fragment.classList.add('close')
                            await wait(500);
                            resolve(Object.values(result))
                        }
                    })
                    lotteryPlayerListDom.innerHTML = ''
                    players.forEach(function (player, index) {
                        let playerFragment = templateLoad('#lottery-template-player-operate-internal', true)
                        let playerOperateDom = playerFragment.querySelector('.player-operate')
                        playerOperateDom.querySelector('.player-avatar').setAttribute('style', `background-image:url(${player.avatar});border-color:${player.color}`)
                        playerOperateDom.querySelector('.player-name').textContent = player.name

                        let operateItemDom = playerOperateDom.querySelector('.operate-item')
                        let confirmListDom = playerOperateDom.querySelectorAll('.game-btn-confirm')
                        let buyDom = confirmListDom.item(0);
                        let abandonDom = confirmListDom.item(1)
                        lotteryPlayerListDom.appendChild(playerFragment)
                        const buyFunc = async () => {
                            let operateDiceDom = operateItemDom.querySelector('.operate-dice img')
                            buyDom.remove();
                            abandonDom.remove()
                            let total = await diceContainer.roll(operateDiceDom)


                            if (total > 4) {
                                let riches = 5000;
                                operateItemDom.innerHTML = `${ICON_CONFIG.RICHES}<span style="color:green">+${riches}元</span>`
                                resultProxy[index] = riches
                            }
                            else {
                                let riches = -1000;
                                operateItemDom.innerHTML = `${ICON_CONFIG.RICHES}<span style="color:red">${riches}元</span>`
                                resultProxy[index] = riches
                            }
                        }
                        buyDom.addEventListener("click", buyFunc)
                        const abandonFunc = () => {
                            operateItemDom.innerHTML = `放弃`
                            resultProxy[index] = 0
                        }
                        abandonDom.addEventListener("click", abandonFunc)
                    })
                })
            }

            return {
                fragment,
                open
            }
        }()

        /**
         * 股票市场
         * @author 	 linyisonger
         * @date 	 2024-12-12
         */
        const stockContainer = await async function () {
            const fragment = templateLoad("#stock-template")
            fragment.classList.add('close')
            const confirmDom = fragment.shadowRoot.querySelector('.game-btn-confirm')
            const stockPlayerListDom = fragment.shadowRoot.querySelector('.stock-player-list')

            /**
             * @param {Player[]} players
             * @return {Promise<number>}
             */
            const open = function (players) {
                fragment.classList.remove('close')
                return new Promise((resolve) => {
                    let resultProxy = observeVariable(async (result) => {
                        if (Object.keys(result).length === players.length) {
                            await wait(500);
                            fragment.classList.add('close')
                            await wait(500);
                            resolve(Object.values(result))
                        }
                    })

                    stockPlayerListDom.innerHTML = ''
                    players.forEach(function (player, index) {
                        let playerFragment = templateLoad('#stock-template-player-operate-internal', true)
                        let playerOperateDom = playerFragment.querySelector('.player-operate')
                        playerOperateDom.querySelector('.player-avatar').setAttribute('style', `background-image:url(${player.avatar});border-color:${player.color}`)
                        playerOperateDom.querySelector('.player-name').textContent = player.name

                        let operateItemDom = playerOperateDom.querySelector('.operate-item')
                        let confirmListDom = playerOperateDom.querySelectorAll('.game-btn-confirm')
                        let buyDom = confirmListDom.item(0);
                        let abandonDom = confirmListDom.item(1)
                        stockPlayerListDom.appendChild(playerFragment)
                        const buyFunc = async () => {
                            let operateDiceDom = operateItemDom.querySelector('.operate-dice img')
                            buyDom.remove();
                            abandonDom.remove()
                            let total = await diceContainer.roll(operateDiceDom)
                            resultProxy[index] = -2000 + total * 1000
                            if (resultProxy[index] > 0) {
                                operateItemDom.innerHTML = `${ICON_CONFIG.RICHES}<span style="color:green">+${resultProxy[index]}元</span>`
                            }
                            else if (resultProxy[index] < 0) {
                                operateItemDom.innerHTML = `${ICON_CONFIG.RICHES}<span style="color:red">${resultProxy[index]}元</span>`
                            }
                            else {
                                operateItemDom.innerHTML = `无变化`
                            }
                        }
                        buyDom.addEventListener("click", buyFunc)
                        const abandonFunc = () => {
                            operateItemDom.innerHTML = `放弃`
                            resultProxy[index] = 0
                        }
                        abandonDom.addEventListener("click", abandonFunc)

                    })
                })
            }

            return {
                fragment,
                open
            }
        }();


        /**
         * 商场
         * @author 	 linyisonger
         * @date 	 2024-12-12
         */
        const mallContainer = await async function () {
            const fragment = templateLoad("#mall-template")
            fragment.classList.add('close')
            const confirmDom = fragment.shadowRoot.querySelector('.game-btn-confirm')
            const mallPlayerListDom = fragment.shadowRoot.querySelector('.mall-player-list')

            /**
             * @param {Player[]} players
             * @return {Promise<number>}
             */
            const open = function (players) {
                fragment.classList.remove('close')
                return new Promise((resolve) => {
                    let resultProxy = observeVariable(async (result) => {
                        if (Object.keys(result).length === players.length) {
                            await wait(500);
                            fragment.classList.add('close')
                            await wait(500);
                            resolve(Object.values(result))
                        }
                    })

                    mallPlayerListDom.innerHTML = ''
                    players.forEach(function (player, index) {
                        let playerFragment = templateLoad('#mall-template-player-operate-internal', true)
                        let playerOperateDom = playerFragment.querySelector('.player-operate')
                        playerOperateDom.querySelector('.player-avatar').setAttribute('style', `background-image:url(${player.avatar});border-color:${player.color}`)
                        playerOperateDom.querySelector('.player-name').textContent = player.name

                        let operateItemDom = playerOperateDom.querySelector('.operate-item')
                        let confirmListDom = playerOperateDom.querySelectorAll('.game-btn-confirm')
                        let buyDom = confirmListDom.item(0);
                        mallPlayerListDom.appendChild(playerFragment)
                        const buyFunc = async () => {
                            let operateDiceDom = operateItemDom.querySelector('.operate-dice img')
                            buyDom.remove();
                            let total = await diceContainer.roll(operateDiceDom)
                            resultProxy[index] = -total * 300
                            operateItemDom.innerHTML = `${ICON_CONFIG.RICHES}<span style="color:red">${resultProxy[index]}元</span>`
                        }
                        buyDom.addEventListener("click", buyFunc)
                    })
                })
            }

            return {
                fragment,
                open
            }
        }();


        /**
         * 画廊
         * @author 	 linyisonger
         * @date 	 2024-12-12
         */
        const galleryContainer = await async function () {
            const fragment = templateLoad("#gallery-template")
            fragment.classList.add('close')
            const confirmDom = fragment.shadowRoot.querySelector('.game-btn-confirm')
            const mallPlayerListDom = fragment.shadowRoot.querySelector('.gallery-player-list')

            /**
             * @param {Player[]} players
             * @return {Promise<{ player: Player, changed:PlayerChanged }[]>}
             */
            const open = function (players) {
                fragment.classList.remove('close')
                return new Promise((resolve) => {
                    /** @type {{[key:string]:{player: Player, changed:PlayerChanged}}} */
                    let resultProxy = observeVariable(async (result) => {
                        if (Object.keys(result).length === players.length) {
                            await wait(500);
                            fragment.classList.add('close')
                            await wait(500);
                            resolve(Object.values(result))
                        }
                    })

                    mallPlayerListDom.innerHTML = ''
                    players.forEach(function (player, index) {
                        let playerFragment = templateLoad('#gallery-template-player-operate-internal', true)
                        let playerOperateDom = playerFragment.querySelector('.player-operate')
                        playerOperateDom.querySelector('.player-avatar').setAttribute('style', `background-image:url(${player.avatar});border-color:${player.color}`)
                        playerOperateDom.querySelector('.player-name').textContent = player.name

                        let operateItemDom = playerOperateDom.querySelector('.operate-item')
                        let confirmListDom = playerOperateDom.querySelectorAll('.game-btn-confirm')
                        let buyDom = confirmListDom.item(0);
                        let abandonDom = confirmListDom.item(1);
                        mallPlayerListDom.appendChild(playerFragment)
                        const buyFunc = async () => {
                            let operateDiceDom = operateItemDom.querySelector('.operate-dice img')
                            buyDom.remove();
                            let total = await diceContainer.roll(operateDiceDom)
                            resultProxy[index] = {
                                player,
                                changed: {
                                    riches: - 3000,
                                    happy: +total
                                }
                            }
                            operateItemDom.innerHTML = `<span style="color:red">+${+total}</span>${ICON_CONFIG.HAPPY}`
                        }
                        buyDom.addEventListener("click", buyFunc)
                        const abandonFunc = () => {
                            operateItemDom.innerHTML = `放弃`
                            resultProxy[index] = null
                        }
                        abandonDom.addEventListener("click", abandonFunc)
                    })
                })
            }

            return {
                fragment,
                open
            }
        }();

        /**
         * 对话框控件
         * @author 	 linyisonger
         * @date 	 2024-12-03
         */
        const dialogContainer = function () {
            const fragment = templateLoad('#dialog-template')
            fragment.classList.add('close')

            class DialogOpenConfig {
                title = ''
                content = ''
                options = []
            }

            /**
             * 打开弹窗
             * @param {DialogOpenConfig} config
             */
            const open = function (config) {
                return new Promise((resolve) => {
                    fragment.classList.remove('close')
                    const titleDom = fragment.shadowRoot.querySelector('.dialog-header-title')
                    const contentDom = fragment.shadowRoot.querySelector('.dialog-body-content')
                    const optionsDom = fragment.shadowRoot.querySelector('.dialog-body-options')
                    titleDom.textContent = config.title;
                    contentDom.innerHTML = config.content
                    optionsDom.innerHTML = '';
                    for (let i = 0; i < config.options.length; i++) {
                        const option = config.options[i];
                        const optionDom = document.createElement('div')
                        optionDom.classList.add('dialog-body-option')
                        optionDom.innerHTML = option
                        optionDom.addEventListener("click", function () {
                            fragment.classList.add('close')
                            resolve(i)
                        })
                        optionsDom.appendChild(optionDom)
                    }
                })

            }

            return {
                fragment,
                open
            }
        }()

        /**
         * 地图控件
         * @author 	 linyisonger
         * @date 	 2024-11-29
         */
        const mapContainer = await async function () {
            let fragment = templateLoad('#map-template')
            let screenWidth = document.body.clientWidth
            let screenHeight = document.body.clientHeight

            // 加载图片背景
            let mapImage = await loadImage('./assets/monopoly-life/map.jpg')
            fragment.style.backgroundImage = `url(${mapImage.src})`
            fragment.style.width = mapImage.width / 2 + 'px'
            fragment.style.height = mapImage.height / 2 + 'px'
            // 溢出距离
            let overflowDistance = 100
            let { lookAt } = dragging(fragment, { minX: -mapImage.width / 2 + screenWidth - overflowDistance, maxX: 0 + overflowDistance, minY: -mapImage.height / 2 + screenHeight - overflowDistance, maxY: 0 + overflowDistance })

            class EmploymentEntranceCondition {
                /** 财富 */
                riches = 0
            }

            /**
             * 数值变化
             * @param {Player} player
             * @param {PlayerChanged} changed
             */
            async function playerChange(player, changed) {
                if (Maths.sum(Object.values(changed)) == 0) {
                    logContainer.log(`玩家[${player.name}]没有任何变化`)
                    return
                }; // 没有发生改变
                let str = `<div class="flex-row-center">玩家[${player.name}]`
                if (changed.riches) {
                    str += `${ICON_CONFIG.RICHES}`
                    if (changed.riches > 0) str += `<span style="color:green">+${changed.riches}元</span>`
                    else str += `<span style="color:red">${changed.riches}元</span>`
                    player.riches += changed.riches
                }
                if (changed.salary) {
                    str += `${ICON_CONFIG.SALARY}`
                    if (changed.salary > 0) str += `<span style="color:green">+${changed.salary}元</span>`
                    else str += `<span style="color:red">${changed.salary}元</span>`
                    player.salary += changed.salary
                }
                if (changed.happy) {
                    if (changed.happy > 0) str += `<span style="color:green">+${changed.happy}</span>`
                    else str += `<span style="color:red">${changed.happy}</span>`
                    str += `${ICON_CONFIG.HAPPY}`
                    player.happy += changed.happy
                }
                if (changed.repute) {
                    if (changed.repute > 0) str += `<span style="color:green">+${changed.repute}</span>`
                    else str += `<span style="color:red">${changed.repute}</span>`
                    str += `${ICON_CONFIG.REPUTE}`
                    player.repute += changed.repute
                }
                if (changed.actionState === PlayerActionEnum.AGAIN) str += '<span style="color:green">再来一次</span>'
                if (changed.actionState === PlayerActionEnum.PAUSE) str += '<span style="color:red">暂停一次</span>'
                str += '</div>'
                logContainer.log(str)
                await playerContainer.upd([player])
            }

            /**
             * 表格展示
             * @param {SceneStation} scene 
             */
            function table(scene) {
                // 是否纯文本
                let isText = !/<td.*?>/.test(this.description);
                let description = this.description;
                if (isText) description = description && `<tr><td>${description}</td></tr>`
                const table = `
                    <table class="log-item-table">
                        <tbody>
                            <tr>
                                <td colspan="${isText ? 1 : 2}">${this.name}</td>
                            </tr>
                            ${description}
                        </tbody>
                    </table>
                `

                console.log(table);
                logContainer.log(`玩家[${scene.curPlayer.name}]触发事件${table}`)
            }

            /**
             * 领取工资
             * @param {SceneStation} scene 
             */
            async function salaryCollection(scene) {
                table.call(this, scene)
                await playerChange(scene.curPlayer, { riches: scene.curPlayer.salary })
            }

            /**
             * 付所得税
             * @param {SceneStation} scene 
             */
            async function payIncomeTax(scene) {
                table.call(this, scene)
                const players = [scene.curPlayer, ...scene.allPlayers]
                for (let i = 0; i < players.length; i++) {
                    const player = players[i];
                    let tax = 0
                    if (player.salary < 3000) tax = -150;
                    else if (player.salary < 6000) tax = -1500
                    else tax = -3000;
                    await playerChange(player, { riches: tax })
                }
            }

            /**
             * 付娱乐税
             * @param {SceneStation} scene 
             */
            async function payEntertainmentTax(scene) {
                table.call(this, scene)
                const players = [scene.curPlayer, ...scene.allPlayers]
                for (let i = 0; i < players.length; i++) {
                    const player = players[i];
                    let tax = 0
                    if (player.happy > 20) tax = -1000;
                    else if (player.happy > 5) tax = -2000;
                    else if (player.happy > 1) tax = -3000;
                    await playerChange(player, { riches: tax })
                }
            }

            /**
            * 就业入口
            * @param {keyof PositionTypeEnum} type 
            */
            function employmentEntrance(type) {
                /** 就业费用 */
                const employmentExpense = {
                    [PositionTypeEnum.AGRICULTURE]: {
                        riches: -500
                    },
                    [PositionTypeEnum.UNIVERSITY]: {
                        riches: -500
                    },
                    [PositionTypeEnum.MINING]: {
                        riches: -5000
                    },
                    [PositionTypeEnum.SPACE]: {
                        riches: -5000
                    },
                    [PositionTypeEnum.MOVIES]: {
                        riches: -1000
                    },
                    [PositionTypeEnum.WELFARE]: {
                        riches: -2000
                    },
                    [PositionTypeEnum.ENTERPRISE]: {
                        riches: -5000
                    },
                    [PositionTypeEnum.VOYAGE]: {
                        riches: -1000
                    }
                }

                /** 经验列表 */
                let experienceList = [
                    new Experience({
                        name: '农业经验',
                        type: ExperienceType.AGRICULTURE
                    }),
                    new Experience({
                        name: '企业经验',
                        type: ExperienceType.ENTERPRISE
                    }),
                    new Experience({
                        name: '航海经验',
                        type: ExperienceType.VOYAGE
                    }),
                    new Experience({
                        name: '公益经验',
                        type: ExperienceType.WELFARE
                    }),
                    new Experience({
                        name: '电影经验',
                        type: ExperienceType.MOVIES
                    }),
                    new Experience({
                        name: '采矿经验',
                        type: ExperienceType.MINING
                    }),
                    new Experience({
                        name: '太空经验',
                        type: ExperienceType.SPACE
                    }),
                ]


                /** 证书列表 */
                let credentialList = [
                    new Credential({
                        name: '普通学位',
                        type: CredentialType.NORMAL
                    }),
                    new Credential({
                        name: '医学学位',
                        type: CredentialType.MEDICINE
                    }),
                    new Credential({
                        name: '法律学位',
                        type: CredentialType.LAW
                    }),
                    new Credential({
                        name: '工程学位',
                        type: CredentialType.ENGINEERING
                    }),
                    new Credential({
                        name: '科学学位',
                        type: CredentialType.SCIENCE
                    }),
                ]

                /**
                * 就业入口
                * @param {SceneStation} scene 
                */
                return async function (scene) {
                    table.call(this, scene)
                    let positionType = PositionTypeEnum[type];
                    // 检查是否有经验 
                    let player = scene.curPlayer
                    let isUniversity = type === "UNIVERSITY";
                    let isHasExp = player.experiences.includes(ex => ex.type == positionType)
                    let isHasCre = player.credentials.includes(cr => CredentialToExperience[cr.type] == positionType)
                    let options = []

                    if (isHasExp || isHasCre) options.push('进入道路')
                    else {
                        let needRiches = employmentExpense[positionType].riches
                        if (player.riches + needRiches < 0) {
                            logContainer.log('钱不够无法进入道路')
                            return
                        }
                        options.push(`进入道路(${needRiches}元)`)
                    }
                    options.push('不进入')

                    let selected = await dialogContainer.open({
                        title: this.name,
                        content: this.description,
                        options
                    })

                    if (options[selected].includes('进入道路')) {
                        player.positionType = positionType
                        player.position = -1;
                        if (!isHasExp) {
                            let experience = experienceList.find(a => a.type == ExperienceType[type])
                            if (experience) player.experiences.push(experience)
                        }

                        logContainer.log(`玩家[${player.name}]选择进入道路`)

                        if (isUniversity) {
                            let selectedCredentialList = Maths.complementarySet(player.credentials.filter(c => c.type != CredentialType.NORMAL), credentialList)
                            selected = await dialogContainer.open({
                                title: '大学进修',
                                content: '请选择大学进修方向',
                                options: selectedCredentialList.map(c => c.name)
                            })
                            if (selected == 0) {
                                logContainer.log('选择[普通学位]薪资+2000元')
                                await playerChange(player, { salary: 2000 })
                            }
                            logContainer.log(`玩家[${player.name}]选择进修[${selectedCredentialList[selected].name}]`)
                            player.credentials.push(selectedCredentialList[selected])
                        }

                        await playerChange(player, employmentExpense[positionType])
                        await playerContainer.upd([player])
                    }
                    else {
                        logContainer.log(`玩家[${player.name}]选择不进入道路`)
                    }
                }
            }


            /**
             * 普通事件
             * @param {(scene:SceneStation)=>PlayerChanged} func
             */
            function ordinaryEvents(func) {
                /**
                 * @param {SceneStation} scene 
                 */
                return async function (scene) {
                    table.call(this, scene)
                    // 存在卡片 处理免疫负面事件 
                    let playerChanged = func(scene);
                    // 免疫卡片
                    let immunityCard = scene.curPlayer.cardHolder.find(c => c.immunity == scene.curPlayer.positionType)
                    // 负面事件
                    let negativeEvent = playerChanged.actionState === PlayerActionEnum.PAUSE || playerChanged.happy < 0 || playerChanged.repute < 0 || playerChanged.riches < 0 || playerChanged.salary < 0
                    if (immunityCard && negativeEvent) {
                        logContainer.log(`玩家[${scene.curPlayer.name}]使用卡片[${immunityCard.name}]免疫负面事件`)
                        return
                    }
                    await playerChange(scene.curPlayer, playerChanged)
                }
            }


            /**
             * 移动事件
             * @param {keyof PositionTypeEnum} type
             * @param {number} index
             */
            function moveStation(type, index) {
                /**
                 * @param {SceneStation} scene 
                 */
                return async function (scene) {

                    let station = null;
                    let player = scene.curPlayer;

                    player.positionType = PositionTypeEnum[type]
                    player.position = index;

                    // 进入外圈
                    if (player.positionType == PositionTypeEnum.LOOP) {
                        station = loopStations[player.position % loopStations.length]; // 外圈站点
                    }

                    let currDom = fragment.shadowRoot.querySelector(`.player-item[data-id="${player.id}"]`);
                    station.dom.appendChild(currDom)
                    lookAt(currDom.parentNode)

                    logContainer.log(`玩家[${scene.curPlayer.name}]移动到[${station.name}]`)
                    await station.stop?.(scene)
                }
            }

            /**
             * 出院名单 
             */
            let dischargeList = []
            /**
             * 离开医院
             * @param {SceneStation} scene 
             */
            async function leaveHospital(scene) {
                let dischargeIndex = dischargeList.findIndex(d => d === scene.curPlayer)
                // 出院
                if (dischargeIndex > -1) {
                    dischargeList.splice(dischargeIndex, 1)
                    return true;
                }
                let diceTotal = await hospitalContainer.open()
                // 再住一回合
                if (diceTotal == 1 || diceTotal == 2) return false;
                // 扣钱下回合出院
                const player = scene.curPlayer;
                const allPlayers = [player, ...scene.allPlayers]
                // 有医学学位的人
                const hasCrePlayers = allPlayers.filter(p => p.credentials.includes(c => c.type == CredentialType.MEDICINE))
                // 薪资的一半
                const halfSalary = ~~(player.salary / 2)
                // 给有医学学位的人
                await playerChange(player, { riches: -halfSalary })


                for (let i = 0; i < hasCrePlayers.length; i++) {
                    const crePlayer = hasCrePlayers[i];
                    await playerChange(player, { riches: ~~(halfSalary / hasCrePlayers.length) })
                }
                dischargeList.push(scene.curPlayer)
                return false;
            }

            /**
             * 停留度假村
             * @param {SceneStation} scene 
             */
            async function stopVacational(scene) {
                table.call(this, scene)
                await playerChange(scene.curPlayer, { happy: 2 })

                let options = [
                    `下回合多停留一次 +3${ICON_CONFIG.HAPPY}`,
                    '下回合离开'
                ]

                let selected = await dialogContainer.open({
                    title: this.name,
                    content: '请选择以下选项',
                    options
                })

                if (selected == 0) {
                    scene.curPlayer.actionState = PlayerActionEnum.PAUSE
                    await playerChange(scene.curPlayer, { happy: 3 })
                }
            }

            // 离开公园人员列表
            let exitParkList = []
            /**
             * 停留公园
             * @param {SceneStation} scene 
             */
            async function leaveOrStopPark() {
                let exitIndex = exitParkList.findIndex(d => d === scene.curPlayer)
                // 离开
                if (exitIndex > -1) {
                    exitParkList.splice(exitIndex, 1)
                    return true;
                }
                let diceTotal = await parkContainer.open()

                // 偶数离开
                if (diceTotal % 2 == 0) {
                    exitParkList.push(scene.curPlayer)
                }

                return false
            }



            /**
             * 停留彩票店
             * @param {SceneStation} scene 
             */
            async function stopLottery(scene) {
                table.call(this, scene)
                let players = [scene.curPlayer, ...scene.allPlayers];
                let result = await lotteryContainer.open(players)
                for (let i = 0; i < players.length; i++) {
                    const player = players[i];
                    result[i] && await playerChange(player, { riches: result[i] })
                }
            }


            /**
             * 停留证券市场
             * @param {SceneStation} scene 
             */
            async function stopStock(scene) {
                table.call(this, scene)
                let players = [scene.curPlayer, ...scene.allPlayers];
                let result = await stockContainer.open(players)
                for (let i = 0; i < players.length; i++) {
                    const player = players[i];
                    result[i] && await playerChange(player, { riches: result[i] })
                }
            }

            /**
             * 停留商场
             * @param {SceneStation} scene 
             */
            async function stopMall(scene) {
                table.call(this, scene)
                let players = [scene.curPlayer, ...scene.allPlayers];
                let result = await mallContainer.open(players)
                for (let i = 0; i < players.length; i++) {
                    const player = players[i];
                    result[i] && await playerChange(player, { riches: result[i] })
                }
            }

            /**
             * 停留画廊
             * @param {SceneStation} scene 
             */
            async function stopGallery(scene) {
                table.call(this, scene)
                let players = [scene.curPlayer, ...scene.allPlayers];
                let result = await galleryContainer.open(players)
                for (let i = 0; i < players.length; i++) {
                    const player = players[i];
                    result[i] && await playerChange(result[i].player, result[i].changed)
                }
            }

            /**
             * 停留许愿井
             * @param {SceneStation} scene 
             */
            async function stopWishing(scene) {
                table.call(this, scene)
                let players = [scene.curPlayer, ...scene.allPlayers];
                let result = await wishingContainer.open(players)
                for (let i = 0; i < players.length; i++) {
                    const player = players[i];
                    result[i] && await playerChange(result[i].player, result[i].changed)
                }
            }



            /**
             * 停留诺贝尔奖
             * @param {SceneStation} scene 
             */
            async function stopNobelPrize(scene) {
                table.call(this, scene)
                const player = scene.curPlayer;
                let happy = 0;
                if (player.repute > 20) happy = +8;
                else if (player.happy > 10) happy = +5;
                else happy = +3;
                await playerChange(player, { happy })
            }


            /**
             * 停留展销会
             * @param {SceneStation} scene 
             */
            async function stopSalesExhibition(scene) {
                table.call(this, scene)
                const player = scene.curPlayer;
                let selected = await dialogContainer.open({
                    title: "汽车展销会",
                    content: "你选择执行以下其中一项：",
                    options: [
                        `<div style="color:red">-2000元</div>并且<div class="flex-row-center" style="color:green">+5${ICON_CONFIG.REPUTE}</div>`,
                        `<div class="flex-row-center" style="color:red">-2${ICON_CONFIG.REPUTE}</div>`,
                    ]

                })
                if (selected == 0) await playerChange(player, { riches: -2000, repute: 5 })
                if (selected == 1) await playerChange(player, { repute: -2 })
            }

            /**
             * 停留机会
             * @param {string} type
             * @param {number} count
             */
            function stopChance(type, count) {
                /**
                 * @param {SceneStation} scene 
                 */
                return async function (scene) {
                    table.call(this, scene)
                    const player = scene.curPlayer;
                    await chanceContainer.open(player, type, count)
                }

            }

            /**
             * 外圈
             * @type {Station[]}
             * @author 	 linyisonger
             * @date 	 2024-12-02
             */
            let loopStations = [
                {
                    id: "1",
                    left: "83.3%",
                    top: "83.3%",
                    width: "15.34%",
                    height: "15.34%",
                    name: '发薪日',
                    description: "经过或者停留此处，可领取与当前薪级相当的薪水",
                    via‌: salaryCollection,
                    stop: salaryCollection
                },
                {
                    id: "2",
                    left: "73.7%",
                    top: "83.3%",
                    width: "9.5%",
                    height: "15.34%",
                    name: '机会',
                    description: "",
                    stop: stopChance(ChanceCardType.CHANCE, 1)
                },
                {
                    id: "3",
                    left: "64.1%",
                    top: "83.3%",
                    width: "9.5%",
                    height: "15.34%",
                    name: '所有人付所得税',
                    description: ` 
                        <tr>
                            <td>薪级</td>
                            <td>需付税款</td>
                        </tr>
                        <tr>
                            <td>3千以下</td>
                            <td>200元</td>
                        </tr>
                        <tr>
                            <td>4千到6千</td>
                            <td>1500元</td>
                        </tr>
                        <tr>
                            <td>6千元以上</td>
                            <td>3000元</td>
                        </tr>
                    `,
                    stop: payIncomeTax
                },
                {
                    id: "4",
                    left: "54.6%",
                    top: "83.3%",
                    width: "9.5%",
                    height: "15.34%",
                    name: '机会',
                    description: "",
                    stop: stopChance(ChanceCardType.CHANCE, 1)
                },
                {
                    id: "5",
                    left: "45.1%",
                    top: "83.3%",
                    width: "9.5%",
                    height: "15.34%",
                    name: '农业开荒就业入口',
                    description: "就业条件：付500元务农资本或已有农业经验",
                    stop: employmentEntrance("AGRICULTURE")
                },
                {
                    id: "6",
                    left: "35.5%",
                    top: "83.3%",
                    width: "9.5%",
                    height: "15.34%",
                    name: '机会',
                    description: "",
                    stop: stopChance(ChanceCardType.CHANCE, 1)
                },
                {
                    id: "7",
                    left: "26%",
                    top: "83.3%",
                    width: "9.5%",
                    height: "15.34%",
                    name: '购买彩票',
                    description: "每人可轮流选择是否执行以下动作：掷一颗骰并根据掷出点数失去或获得金钱",
                    stop: stopLottery
                },
                {
                    id: "8",
                    left: "16.4%",
                    top: "83.3%",
                    width: "9.5%",
                    height: "15.34%",
                    name: '大学进修就业入口',
                    description: "入学条件：付500元学费",
                    stop: employmentEntrance("UNIVERSITY")
                },
                {
                    id: "9",
                    left: "1%",
                    top: "83.3%",
                    width: "15.34%",
                    height: "15.34%",
                    name: '医院',
                    description: "",
                    leave: leaveHospital
                },
                {
                    id: "10",
                    left: "1%",
                    top: "73.6%",
                    width: "15.34%",
                    height: "9.5%",
                    name: '所有人付娱乐费',
                    description: `
                        <tr>
                            <td><div class="flex-row-center">所持有${ICON_CONFIG.HAPPY}</div></td>
                            <td>需付款</td>
                        </tr>
                        <tr>
                            <td><div class="flex-row-center">1~5${ICON_CONFIG.HAPPY}</div></td>
                            <td>3000元</td>
                        </tr>
                        <tr>
                            <td><div class="flex-row-center">6~20${ICON_CONFIG.HAPPY}</div></td>
                            <td>2000元</td>
                        </tr>
                        <tr>
                            <td><div class="flex-row-center">21${ICON_CONFIG.HAPPY}以上</div></td>
                            <td>1000元</td>
                        </tr>
                    `,
                    stop: payEntertainmentTax
                },
                {
                    id: "11",
                    left: "1%",
                    top: "64%",
                    width: "15.34%",
                    height: "9.5%",
                    name: '机会',
                    description: "",
                    stop: stopChance(ChanceCardType.CHANCE, 1)
                },
                {
                    id: "12",
                    left: "1%",
                    top: "54.7%",
                    width: "15.34%",
                    height: "9.2%",
                    name: '企业经营就业入口',
                    description: "就业条件：付5000元雇佣员工或已有普通学位或已有企业经验",
                    stop: employmentEntrance("ENTERPRISE")
                },
                {
                    id: "13",
                    left: "1%",
                    top: "45.2%",
                    width: "15.34%",
                    height: "9.4%",
                    name: '机会',
                    description: "",
                    stop: stopChance(ChanceCardType.CHANCE, 1)
                },
                {
                    id: "14",
                    left: "1%",
                    top: "35.6%",
                    width: "15.34%",
                    height: "9.5%",
                    name: '画廊',
                    description: "",
                    stop: stopGallery
                },
                {
                    id: "15",
                    left: "1%",
                    top: "26.2%",
                    width: "15.34%",
                    height: "9.4%",
                    name: '航海就业入口',
                    description: ``,
                    stop: employmentEntrance("VOYAGE")
                },
                {
                    id: "16",
                    left: "1%",
                    top: "16.6%",
                    width: "15.34%",
                    height: "9.4%",
                    name: '机会',
                    description: "",
                    stop: stopChance(ChanceCardType.CHANCE, 1)
                },
                {
                    id: "17",
                    left: "1%",
                    top: "1.2%",
                    width: "15.34%",
                    height: "15.34%",
                    name: '三亚度假',
                    description: `
                        <tr>
                            <td>停留此处+2${ICON_CONFIG.HAPPY}</td>
                        </tr>
                        <tr>
                            <td>可选择下回合多停留一次+3${ICON_CONFIG.HAPPY}(限额外停留一次)</td>
                        </tr>
                    `,
                    stop: stopVacational
                },
                {
                    id: "18",
                    left: "16.5%",
                    top: "1.2%",
                    width: "9.4%",
                    height: "15.34%",
                    name: '机会',
                    description: "",
                    stop: stopChance(ChanceCardType.CHANCE, 1)
                },
                {
                    id: "19",
                    left: "26%",
                    top: "1.2%",
                    width: "9.4%",
                    height: "15.34%",
                    name: '汽车展销会',
                    description: "",
                    stop: stopSalesExhibition
                },
                {
                    id: "20",
                    left: "35.5%",
                    top: "1.2%",
                    width: "9.4%",
                    height: "15.34%",
                    name: '公益事业就业入口',
                    description: "",
                    stop: employmentEntrance("WELFARE")
                },
                {
                    id: "21",
                    left: "45%",
                    top: "1.2%",
                    width: "9.6%",
                    height: "15.34%",
                    name: '机会',
                    description: "",
                    stop: stopChance(ChanceCardType.CHANCE, 1)
                },
                {
                    id: "22",
                    left: "54.7%",
                    top: "1.2%",
                    width: "9.4%",
                    height: "15.34%",
                    name: '商场',
                    description: "",
                    stop: stopMall
                },
                {
                    id: "23",
                    left: "64.2%",
                    top: "1.2%",
                    width: "9.4%",
                    height: "15.34%",
                    name: '电影拍摄就业入口',
                    description: "",
                    stop: employmentEntrance("MOVIES")
                },
                {
                    id: "24",
                    left: "73.7%",
                    top: "1.2%",
                    width: "9.4%",
                    height: "15.34%",
                    name: '机会',
                    description: "",
                    stop: stopChance(ChanceCardType.CHANCE, 1)
                },
                {
                    id: "25",
                    left: "83.2%",
                    top: "1.2%",
                    width: "15.44%",
                    height: "15.34%",
                    name: '公园板凳',
                    description: "不可使用机会卡或经验卡离开此格，投掷一骰，若点数为双数，下回合即可离开",
                    stop: leaveOrStopPark,
                    leave: leaveOrStopPark
                },
                {
                    id: "26",
                    left: "83.2%",
                    top: "16.6%",
                    width: "15.44%",
                    height: "9.5%",
                    name: '机会',
                    description: "",
                    stop: stopChance(ChanceCardType.CHANCE, 1)
                },
                {
                    id: "27",
                    left: "83.2%",
                    top: "26.2%",
                    width: "15.44%",
                    height: "9.5%",
                    name: '股票市场',
                    description: "",
                    stop: stopStock
                },
                {
                    id: "28",
                    left: "83.2%",
                    top: "35.8%",
                    width: "15.44%",
                    height: "9.4%",
                    name: '采矿开发就业入口',
                    description: "",
                    stop: employmentEntrance("MINING")

                },
                {
                    id: "29",
                    left: "83.2%",
                    top: "45.3%",
                    width: "15.44%",
                    height: "9.4%",
                    name: '机会',
                    description: "",
                    stop: stopChance(ChanceCardType.CHANCE, 1)
                },
                {
                    id: "30",
                    left: "83.2%",
                    top: "54.8%",
                    width: "15.44%",
                    height: "9.4%",
                    name: '许愿井',
                    description: "",
                    stop: stopWishing
                },
                {
                    id: "31",
                    left: "83.2%",
                    top: "64.3%",
                    width: "15.44%",
                    height: "9.4%",
                    name: '太空探险就业入口',
                    description: "",
                    stop: employmentEntrance("SPACE")
                },
                {
                    id: "32",
                    left: "83.2%",
                    top: "73.8%",
                    width: "15.44%",
                    height: "9.4%",
                    name: '获诺贝尔奖',
                    description: `
                        <tr>
                            <td><div class="flex-row-center">拥有的${ICON_CONFIG.REPUTE}</div></td>
                            <td>获得</td>
                        </tr>
                        <tr>
                            <td><div class="flex-row-center">10个以下</div></td>
                            <td>+3${ICON_CONFIG.HAPPY}</td>
                        </tr>
                        <tr>
                            <td><div class="flex-row-center">11到20个</div></td>
                            <td>+5${ICON_CONFIG.HAPPY}</td>
                        </tr>
                        <tr>
                            <td><div class="flex-row-center">21个以上</div></td>
                            <td>+8${ICON_CONFIG.HAPPY}</td>
                        </tr>
                    `,
                    stop: stopNobelPrize
                }
            ].map((item) => new Station(item))

            /**
             * 内圈
             * @type {{ [x: number]: Station[];}}
             * @author 	 linyisonger
             * @date 	 2024-11-30
             */
            let insideStations = {
                // 农业
                [PositionTypeEnum.AGRICULTURE]: [
                    {
                        id: "1",
                        left: "45.1%",
                        top: "78.5%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '土地肥沃适合耕种',
                        description: `+2${ICON_CONFIG.HAPPY}`,
                        stop: ordinaryEvents((scene) => { return { happy: 2 } })
                    },
                    {
                        id: "2",
                        left: "45.1%",
                        top: "73.7%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '研发新种改良作物',
                        description: "抽两张经验卡",
                        stop: stopChance(ChanceCardType.EXPERIENCE, 2)
                    },
                    {
                        id: "3",
                        left: "45.1%",
                        top: "69.01%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '发展环保农业',
                        description: `+5${ICON_CONFIG.HAPPY}`,
                        stop: ordinaryEvents((scene) => { return { happy: 5 } })
                    },
                    {
                        id: "4",
                        left: "45.1%",
                        top: "64.31%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '夏季收成',
                        description: "+🎲×1000元",
                        stop: ordinaryEvents((scene) => { return { riches: scene.dicePoints * 1000 } })
                    },
                    {
                        id: "5",
                        left: "40.4%",
                        top: "64.31%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '有机饲料家畜健康',
                        description: `+2${ICON_CONFIG.REPUTE}4${ICON_CONFIG.HAPPY}`,
                        stop: ordinaryEvents((scene) => { return { happy: 4, repute: 2 } })
                    },
                    {
                        id: "6",
                        left: "35.6%",
                        top: "64.31%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '发生家畜传染病',
                        description: "-3000元",
                        stop: ordinaryEvents((scene) => { return { riches: -3000 } })
                    },
                    {
                        id: "7",
                        left: "35.6%",
                        top: "69.11%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '秋季大丰收',
                        description: "+5000元",
                        stop: ordinaryEvents((scene) => { return { riches: 5000 } })
                    },
                    {
                        id: "8",
                        left: "35.6%",
                        top: "73.86%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '大雪导致家禽冻死',
                        description: `-${ICON_CONFIG.HAPPY}/2`,
                        stop: ordinaryEvents((scene) => { return { happy: ~~(-scene.curPlayer.happy / 2) } })
                    },
                    {
                        id: "9",
                        left: "35.6%",
                        top: "78.56%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '成立生技实验室',
                        description: "加一千元薪级别",
                        stop: ordinaryEvents((scene) => { return { salary: 1000 } })
                    }
                ].map((item) => new Station(item)),
                // 大学
                [PositionTypeEnum.UNIVERSITY]: [
                    {
                        id: "1",
                        left: "21.2%",
                        top: "78.5%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '结识同学',
                        description: `+4${ICON_CONFIG.HAPPY}`,
                        stop: ordinaryEvents((scene) => { return { happy: 4 } })
                    },
                    {
                        id: "2",
                        left: "21.2%",
                        top: "73.65%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '期中考睡过头',
                        description: "暂停一次",
                        stop: ordinaryEvents((scene) => { return { actionState: PlayerActionEnum.PAUSE } })
                    },
                    {
                        id: "3",
                        left: "26.0%",
                        top: "73.65%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '代表学校参加比赛',
                        description: `+2${ICON_CONFIG.REPUTE}`,
                        stop: ordinaryEvents((scene) => { return { repute: 2 } })
                    },
                    {
                        id: "4",
                        left: "26.0%",
                        top: "68.85%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '交错报告',
                        description: `-🎲×${ICON_CONFIG.HAPPY}`,
                        stop: ordinaryEvents((scene) => { return { happy: -scene.dicePoints } })
                    },
                    {
                        id: "5",
                        left: "26.0%",
                        top: "64.15%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '成绩优异获奖学金',
                        description: "+1000元",
                        stop: ordinaryEvents((scene) => { return { riches: 1000 } })
                    },
                    {
                        id: "6",
                        left: "21.2%",
                        top: "64.15%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '获选为学生会长',
                        description: `+6${ICON_CONFIG.REPUTE}`,
                        stop: ordinaryEvents((scene) => { return { repute: 6 } })
                    },
                    {
                        id: "7",
                        left: "16.5%",
                        top: "64.15%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '毕业旅行',
                        description: "抽两张机会卡",
                        stop: stopChance(ChanceCardType.CHANCE, 2)
                    },
                ].map((item) => new Station(item)),
                // 企业
                [PositionTypeEnum.ENTERPRISE]: [
                    {
                        id: "1",
                        left: "16.5%",
                        top: "54.7%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '通过实习',
                        description: "抽两张经验卡",
                        stop: stopChance(ChanceCardType.EXPERIENCE, 2)
                    },
                    {
                        id: "2",
                        left: "21.2%",
                        top: "54.7%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '简报优异',
                        description: `+4${ICON_CONFIG.HAPPY}`,
                        stop: ordinaryEvents((scene) => { return { happy: 4 } })
                    },
                    {
                        id: "3",
                        left: "26%",
                        top: "54.7%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '全勤奖金',
                        description: "+3000元",
                        stop: ordinaryEvents((scene) => { return { riches: 3000 } })
                    },
                    {
                        id: "4",
                        left: "30.8%",
                        top: "54.7%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '误会客户失去合约',
                        description: "暂停一次",
                        stop: ordinaryEvents((scene) => { return { actionState: PlayerActionEnum.PAUSE } })
                    },
                    {
                        id: "5",
                        left: "30.8%",
                        top: "50%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '研发新品备受瞩目',
                        description: `+6${ICON_CONFIG.REPUTE}`,
                        stop: ordinaryEvents((scene) => { return { repute: 6 } })
                    },
                    {
                        id: "6",
                        left: "30.8%",
                        top: "45.2%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '成功扩展网店商场',
                        description: "+8000元",
                        stop: ordinaryEvents((scene) => { return { riches: 8000 } })
                    },
                    {
                        id: "7",
                        left: "30.8%",
                        top: "40.4%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '投资准确',
                        description: "+4000元",
                        stop: ordinaryEvents((scene) => { return { riches: 4000 } })
                    },
                    {
                        id: "8",
                        left: "26.1%",
                        top: "40.4%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '表现突出担任经理',
                        description: `+2${ICON_CONFIG.REPUTE}4${ICON_CONFIG.HAPPY}`,
                        stop: ordinaryEvents((scene) => { return { repute: 2, happy: 4 } })
                    },
                    {
                        id: "9",
                        left: "21.4%",
                        top: "40.4%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '金融风暴周转失灵',
                        description: "马上移到公园",
                        stop: moveStation("LOOP", 24)
                    },
                    {
                        id: "10",
                        left: "21.4%",
                        top: "45.2%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '品管不周客户退货',
                        description: `-${ICON_CONFIG.RICHES}/2`,
                        stop: ordinaryEvents((scene) => { return { riches: ~~(-scene.curPlayer.riches / 2) } })
                    },
                    {
                        id: "11",
                        left: "16.55%",
                        top: "45.2%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '年终奖金',
                        description: "+6000元",
                        stop: ordinaryEvents((scene) => { return { riches: 6000 } })
                    },
                ].map((item) => new Station(item)),
                // 航海
                [PositionTypeEnum.VOYAGE]: [
                    {
                        id: "1",
                        left: "16.5%",
                        top: "30.9%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '严重晕船',
                        description: "马上移到医院",
                        stop: moveStation("LOOP", 8)
                    },
                    {
                        id: "2",
                        left: "21.25%",
                        top: "30.9%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '与海豚一同玩乐',
                        description: `+2${ICON_CONFIG.HAPPY}`,
                        stop: ordinaryEvents((scene) => { return { happy: 2 } })
                    },
                    {
                        id: "3",
                        left: "26%",
                        top: "30.9%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '发现神秘海岛',
                        description: "抽两张经验卡",
                        stop: stopChance(ChanceCardType.EXPERIENCE, 2)
                    },
                    {
                        id: "4",
                        left: "30.7%",
                        top: "30.9%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '遭遇暴风雨',
                        description: `-🎲×${ICON_CONFIG.HAPPY}`,
                        stop: ordinaryEvents((scene) => { return { happy: -scene.dicePoints } })
                    },
                    {
                        id: "5",
                        left: "30.8%",
                        top: "26.2%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '击退海盗救回同伴',
                        description: `+6${ICON_CONFIG.REPUTE}`,
                        stop: ordinaryEvents((scene) => { return { repute: 6 } })
                    },
                    {
                        id: "6",
                        left: "30.8%",
                        top: "21.45%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '荣升大副',
                        description: `+6${ICON_CONFIG.REPUTE}`,
                        stop: ordinaryEvents((scene) => { return { repute: 6 } })
                    },
                    {
                        id: "7",
                        left: "26%",
                        top: "21.45%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '打捞深海宝藏',
                        description: "+🎲×1000元",
                        stop: ordinaryEvents((scene) => { return { riches: scene.dicePoints * 1000 } })
                    },
                    {
                        id: "8",
                        left: "21.3%",
                        top: "21.45%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '雾中迷航顺利脱险',
                        description: `+2${ICON_CONFIG.REPUTE}4${ICON_CONFIG.HAPPY}`,
                        stop: ordinaryEvents((scene) => { return { repute: 2, happy: 4 } })
                    },
                    {
                        id: "9",
                        left: "21.3%",
                        top: "16.75%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '平安返乡',
                        description: "加一千元薪级",
                        stop: ordinaryEvents((scene) => { return { salary: 1000 } })
                    },
                ].map((item) => new Station(item)),
                // 公益
                [PositionTypeEnum.WELFARE]: [
                    {
                        id: "1",
                        left: "40.3%",
                        top: "16.75%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '清扫街道美化市容',
                        description: `+2${ICON_CONFIG.HAPPY}`,
                        stop: ordinaryEvents((scene) => { return { happy: 2 } })
                    },
                    {
                        id: "2",
                        left: "40.3%",
                        top: "21.5%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '植树造林',
                        description: `+🎲×${ICON_CONFIG.HAPPY}`,
                        stop: ordinaryEvents((scene) => { return { happy: scene.dicePoints } })
                    },
                    {
                        id: "3",
                        left: "40.3%",
                        top: "26.2%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '倡导环保',
                        description: `+🎲×${ICON_CONFIG.REPUTE}`,
                        stop: ordinaryEvents((scene) => { return { repute: scene.dicePoints } })
                    },
                    {
                        id: "4",
                        left: "40.3%",
                        top: "30.9%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '协助医师下乡义诊',
                        description: `+4${ICON_CONFIG.REPUTE}`,
                        stop: ordinaryEvents((scene) => { return { repute: 4 } })
                    },
                    {
                        id: "5",
                        left: "45%",
                        top: "30.9%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '热心过度体力不支',
                        description: "马上移到医院",
                        stop: moveStation("LOOP", 8)
                    },
                    {
                        id: "6",
                        left: "49.8%",
                        top: "30.9%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '建设农村电力管线',
                        description: "抽两张经验卡",
                        stop: stopChance(ChanceCardType.EXPERIENCE, 2)
                    },
                    {
                        id: "7",
                        left: "54.6%",
                        top: "30.9%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '捐款助学',
                        description: "+🎲×1000元",
                        stop: ordinaryEvents((scene) => { return { riches: scene.dicePoints * 1000 } })
                    },
                    {
                        id: "8",
                        left: "54.6%",
                        top: "26.2%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '爱心捐赠弱势群体',
                        description: `+6${ICON_CONFIG.REPUTE}`,
                        stop: ordinaryEvents((scene) => { return { repute: 6 } })
                    },
                    {
                        id: "9",
                        left: "54.6%",
                        top: "21.5%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '遭遇天灾',
                        description: "马上移到公园",
                        stop: moveStation("LOOP", 24)
                    },
                    {
                        id: "10",
                        left: "49.8%",
                        top: "21.5%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '深入灾区救助难民',
                        description: `+10${ICON_CONFIG.HAPPY}`,
                        stop: ordinaryEvents((scene) => { return { happy: 10 } })
                    },
                    {
                        id: "11",
                        left: "49.8%",
                        top: "16.7%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '造桥铺路',
                        description: `+3${ICON_CONFIG.HAPPY}3${ICON_CONFIG.REPUTE}`,
                        stop: ordinaryEvents((scene) => { return { happy: 3, repute: 3 } })
                    },
                ].map((item) => new Station(item)),
                // 电影
                [PositionTypeEnum.MOVIES]: [
                    {
                        id: "1",
                        left: "64.2%",
                        top: "16.75%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '参加考试',
                        description: "抽两张经验卡",
                        stop: stopChance(ChanceCardType.EXPERIENCE, 2)
                    },
                    {
                        id: "2",
                        left: "64.2%",
                        top: "21.5%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '广告出道一鸣惊人',
                        description: `+4${ICON_CONFIG.REPUTE}`,
                        stop: ordinaryEvents((scene) => { return { repute: 4 } })
                    },
                    {
                        id: "3",
                        left: "64.2%",
                        top: "26.3%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '获得电影演出机会',
                        description: "+🎲×1000元",
                        stop: ordinaryEvents((scene) => { return { riches: scene.dicePoints * 1000 } })
                    },
                    {
                        id: "4",
                        left: "64.2%",
                        top: "31%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '绯闻曝光',
                        description: `-${ICON_CONFIG.HAPPY}/2`,
                        stop: ordinaryEvents((scene) => { return { happy: ~~(-scene.curPlayer.happy / 2) } })
                    },
                    {
                        id: "5",
                        left: "68.9%",
                        top: "31%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '粉丝成立后援会',
                        description: `+3${ICON_CONFIG.REPUTE}3${ICON_CONFIG.HAPPY}`,
                        stop: ordinaryEvents((scene) => { return { happy: 3, repute: 3 } })
                    },
                    {
                        id: "6",
                        left: "73.6%",
                        top: "31%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '不实报道丑化形象',
                        description: `-${ICON_CONFIG.REPUTE}/2`,
                        stop: ordinaryEvents((scene) => { return { repute: ~~(-scene.curPlayer.repute / 2) } })
                    },
                    {
                        id: "7",
                        left: "73.6%",
                        top: "26.3%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '电影上映大受欢迎',
                        description: "加一千元薪级",
                        stop: ordinaryEvents((scene) => { return { salary: 1000 } })
                    },
                    {
                        id: "8",
                        left: "73.6%",
                        top: "21.5%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '成为杂志封面人物',
                        description: "+6000元",
                        stop: ordinaryEvents((scene) => { return { riches: 6000 } })
                    },
                    {
                        id: "9",
                        left: "78.4%",
                        top: "21.5%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '荣获金马奖',
                        description: `+4${ICON_CONFIG.REPUTE}4${ICON_CONFIG.HAPPY}`,
                        stop: ordinaryEvents((scene) => { return { happy: 4, repute: 4 } })
                    },
                ].map((item) => new Station(item)),
                // 采矿
                [PositionTypeEnum.MINING]: [
                    {
                        id: "1",
                        left: "78.4%",
                        top: "40.5%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '矿区采样',
                        description: "-2000元",
                        stop: ordinaryEvents((scene) => { return { riches: -2000 } })
                    },
                    {
                        id: "2",
                        left: "73.6%",
                        top: "40.5%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '坑道整顿',
                        description: "再行动一次",
                        stop: ordinaryEvents((scene) => { return { actionState: PlayerActionEnum.AGAIN } })
                    },
                    {
                        id: "3",
                        left: "68.8%",
                        top: "40.5%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '山崩',
                        description: "暂定一次",
                        stop: ordinaryEvents((scene) => { return { actionState: PlayerActionEnum.PAUSE } })
                    },
                    {
                        id: "4",
                        left: "64.1%",
                        top: "40.5%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '发现黄金',
                        description: `+2${ICON_CONFIG.REPUTE}4${ICON_CONFIG.HAPPY}`,
                        stop: ordinaryEvents((scene) => { return { happy: 4, repute: 2 } })
                    },
                    {
                        id: "5",
                        left: "64.1%",
                        top: "45.3%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '地质分析',
                        description: "-🎲×1000元",
                        stop: ordinaryEvents((scene) => { return { riches: -scene.dicePoints * 1000 } })
                    },
                    {
                        id: "6",
                        left: "64.1%",
                        top: "50%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '意外发现文明遗迹',
                        description: `+10${ICON_CONFIG.HAPPY}`,
                        stop: ordinaryEvents((scene) => { return { happy: 10 } })
                    },
                    {
                        id: "7",
                        left: "64.1%",
                        top: "54.7%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '引发尘暴',
                        description: "马上移到医院",
                        stop: moveStation("LOOP", 8)
                    },
                    {
                        id: "8",
                        left: "68.8%",
                        top: "54.7%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '发现石油',
                        description: "+8000元",
                        stop: ordinaryEvents((scene) => { return { riches: 8000 } })
                    },
                    {
                        id: "9",
                        left: "73.6%",
                        top: "54.7%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '研发秘密输送管线',
                        description: "抽两张经验卡",
                        stop: stopChance(ChanceCardType.EXPERIENCE, 2)
                    },
                    {
                        id: "10",
                        left: "73.6%",
                        top: "50%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '发现钻石',
                        description: "+6000元",
                        stop: ordinaryEvents((scene) => { return { riches: 6000 } })
                    },
                    {
                        id: "11",
                        left: "78.4%",
                        top: "50%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '成立跨国采矿集团',
                        description: "+10000元",
                        stop: ordinaryEvents((scene) => { return { riches: 10000 } })
                    },
                ].map((item) => new Station(item)),
                // 航天
                [PositionTypeEnum.SPACE]: [
                    {
                        id: "1",
                        left: "78.4%",
                        top: "64.2%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '协助建造航天飞机',
                        description: `+6${ICON_CONFIG.HAPPY}`,
                        stop: ordinaryEvents((scene) => { return { happy: 6 } })
                    },
                    {
                        id: "2",
                        left: "73.6%",
                        top: "64.2%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '成功发射航天飞机',
                        description: "抽两张经验卡",
                        stop: stopChance(ChanceCardType.EXPERIENCE, 2)
                    },
                    {
                        id: "3",
                        left: "68.9%",
                        top: "64.2%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '压力不适导致呕吐',
                        description: `-4${ICON_CONFIG.REPUTE}`,
                        stop: ordinaryEvents((scene) => { return { repute: -4 } })
                    },
                    {
                        id: "4",
                        left: "64.2%",
                        top: "64.2%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '通讯故障',
                        description: "-3000元",
                        stop: ordinaryEvents((scene) => { return { riches: -3000 } })
                    },
                    {
                        id: "5",
                        left: "59.5%",
                        top: "64.2%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '紧急抢修',
                        description: `+2${ICON_CONFIG.REPUTE}4${ICON_CONFIG.HAPPY}`,
                        stop: ordinaryEvents((scene) => { return { repute: 2, happy: 4 } })
                    },
                    {
                        id: "6",
                        left: "54.7%",
                        top: "64.2%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '闪避陨石',
                        description: `+6${ICON_CONFIG.REPUTE}`,
                        stop: ordinaryEvents((scene) => { return { repute: 6 } })
                    },
                    {
                        id: "7",
                        left: "54.7%",
                        top: "69%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '登陆火星',
                        description: `+🎲×${ICON_CONFIG.HAPPY}`,
                        stop: ordinaryEvents((scene) => { return { happy: scene.dicePoints } })
                    },
                    {
                        id: "8",
                        left: "54.7%",
                        top: "73.8%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '发现火星特有细菌',
                        description: "+4000元",
                        stop: ordinaryEvents((scene) => { return { riches: 4000 } })
                    },
                    {
                        id: "9",
                        left: "59.5%",
                        top: "73.8%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '测量地形',
                        description: `+10${ICON_CONFIG.HAPPY}`,
                        stop: ordinaryEvents((scene) => { return { happy: 10 } })
                    },
                    {
                        id: "10",
                        left: "64.2%",
                        top: "73.8%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '发现神秘文明遗迹',
                        description: `+10${ICON_CONFIG.REPUTE}`,
                        stop: ordinaryEvents((scene) => { return { repute: 10 } })
                    },
                    {
                        id: "11",
                        left: "68.9%",
                        top: "73.8%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '航天服破裂',
                        description: "马上移到医院",
                        stop: moveStation("LOOP", 8)
                    },
                    {
                        id: "12",
                        left: "73.6%",
                        top: "73.8%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '返航地球',
                        description: `+6${ICON_CONFIG.REPUTE}+4${ICON_CONFIG.HAPPY}`,
                        stop: ordinaryEvents((scene) => { return { repute: 6, happy: 4 } })

                    },
                    {
                        id: "13",
                        left: "73.6%",
                        top: "78.5%",
                        width: "4.7%",
                        height: "4.7%",
                        name: '将经历写成小说',
                        description: "+6000元",
                        stop: ordinaryEvents((scene) => { return { riches: 6000 } })

                    },
                ].map((item) => new Station(item)),
            }

            /**
             * 离开内圈到达外圈的位置
             */
            let exitInsidePosition = {
                // 农业
                [PositionTypeEnum.AGRICULTURE]: 5,
                // 大学
                [PositionTypeEnum.UNIVERSITY]: 10,
                // 企业
                [PositionTypeEnum.ENTERPRISE]: 12,
                // 航海
                [PositionTypeEnum.VOYAGE]: 17,
                // 公益
                [PositionTypeEnum.WELFARE]: 20,
                // 电影
                [PositionTypeEnum.MOVIES]: 25,
                // 采矿 
                [PositionTypeEnum.MINING]: 28,
                // 航天
                [PositionTypeEnum.SPACE]: 1
            }

            let scene = new SceneStation();

            for (const station of [...loopStations, ...Object.values(insideStations).flat()]) {
                let stationDom = document.createElement('div');
                stationDom.classList.add('station-item')
                stationDom.style.left = station.left
                stationDom.style.top = station.top
                stationDom.style.width = station.width
                stationDom.style.height = station.height
                fragment.shadowRoot.appendChild(stationDom)
                station.dom = stationDom
            }

            /**
             * 玩家加入
             * @param {Player[]} players
             */
            let join = async (players) => {
                scene.allPlayers = players;
                for (const player of players) {
                    // 起点出发
                    loopStations[0].dom.innerHTML += `
                        <div class="player-item" data-id="${player.id}">
                            <div class="player-avatar" style="background-image:url(${player.avatar});border-color:${player.color}"></div>
                        </div>
                    `
                }
            }
            /**
             * 玩家移动
             * @param {Player} player
             * @param {number} count
             */
            let move = async (player, count) => {
                // count = 7 
                scene.curPlayer = player;
                scene.dicePoints = count;
                let currDom = fragment.shadowRoot.querySelector(`.player-item[data-id="${player.id}"]`);
                for (let i = 0; i < count; i++) {
                    let isExitInsideStation = false // 是否离开内环路
                    await wait(500); // 测试加速 
                    player.position++
                    let station = null
                    let insideStation = insideStations[player.positionType]
                    // 进入内圈
                    if (insideStation) {
                        if (player.position < insideStation.length) { // 在内圈转动
                            station = insideStation[player.position]  // 内圈站点
                        }
                        else {
                            player.position = exitInsidePosition[player.positionType] // 初始化进入外圈的位置
                            player.positionType = PositionTypeEnum.LOOP // 切换外圈类型
                            isExitInsideStation = true; // 离开内环路
                        }
                    }

                    // 进入外圈
                    if (player.positionType == PositionTypeEnum.LOOP) {
                        station = loopStations[player.position % loopStations.length]; // 外圈站点
                    }

                    station.dom.appendChild(currDom)

                    // 离开内环路
                    if (isExitInsideStation && station.name == '机会') {
                        await stopChance(ChanceCardType.EXPERIENCE, 1).call(station, scene)
                    }

                    if (count - i > 1) await station.via‌?.(scene) // 经过
                    else await station.stop?.(scene)  // 停留 
                    lookAt(currDom.parentNode) // 镜头跟随
                }
                await wait(1000);
            }


            /**
             * 玩家离开
             * @param {Player} player
             */
            let leave = async (player) => {
                // 进入外圈
                if (player.positionType == PositionTypeEnum.LOOP) {
                    let station = loopStations[player.position % loopStations.length]; // 外圈站点
                    scene.curPlayer = player;
                    if (station.leave === undefined) return true;
                    return await station.leave(scene)
                }
                return true;
            }



            /**
             * 下一个玩家
             * @param {Player} player 
             */
            let next = async (player) => {
                let lastDom = fragment.shadowRoot.querySelector('.player-item.active');
                let currDom = fragment.shadowRoot.querySelector(`.player-item[data-id="${player.id}"]`);
                if (lastDom) lastDom.classList.remove('active')
                currDom.classList.add('active')
                lookAt(currDom.parentNode) // 镜头跟随
            }


            /**
             * 闪现事件
             * @param {Player} player
             * @param {keyof PositionTypeEnum} type
             * @param {number} index
             */
            let flash = async (player, type, index) => {
                scene.curPlayer = player;
                await moveStation(type, index)(scene)
            }


            return {
                fragment,
                join,
                move,
                next,
                leave,
                flash
            }
        }()

        /**
         * 许愿井
         * @author 	 linyisonger
         * @date 	 2024-12-12
         */
        const wishingContainer = await async function () {
            const fragment = templateLoad("#wishing-template")
            fragment.classList.add('close')
            const confirmDom = fragment.shadowRoot.querySelector('.game-btn-confirm')
            const playerListDom = fragment.shadowRoot.querySelector('.wishing-player-list')

            /**
             * @param {Player[]} players
             * @return {Promise<{ player: Player, changed:PlayerChanged }[]>}
             */
            const open = function (players) {
                fragment.classList.remove('close')
                return new Promise((resolve) => {
                    /** @type {{[key:string]:{player: Player, changed:PlayerChanged}}} */
                    let resultProxy = observeVariable(async (result) => {
                        if (Object.keys(result).length === players.length) {
                            await wait(500);
                            fragment.classList.add('close')
                            await wait(500);
                            resolve(Object.values(result))
                        }
                    })

                    playerListDom.innerHTML = ''
                    players.forEach(function (player, index) {
                        let playerFragment = templateLoad('#wishing-template-player-operate-internal', true)
                        let playerOperateDom = playerFragment.querySelector('.player-operate')
                        playerOperateDom.querySelector('.player-avatar').setAttribute('style', `background-image:url(${player.avatar});border-color:${player.color}`)
                        playerOperateDom.querySelector('.player-name').textContent = player.name

                        let operateItemDom = playerOperateDom.querySelector('.operate-item')
                        let confirmListDom = playerOperateDom.querySelectorAll('.game-btn-confirm')
                        let leftSelectedListDom = playerOperateDom.querySelectorAll('.select-item.left')
                        let rightSelectedListDom = playerOperateDom.querySelectorAll('.select-item.right')

                        leftSelectedListDom.forEach(function (selectedDom) {
                            selectedDom.addEventListener("click", (ev) => {
                                leftSelectedListDom.forEach(item => item.classList.remove('active'))
                                selectedDom.classList.add('active')
                            })
                        })
                        rightSelectedListDom.forEach(function (selectedDom) {
                            selectedDom.addEventListener("click", (ev) => {
                                rightSelectedListDom.forEach(item => item.classList.remove('active'))
                                selectedDom.classList.add('active')
                            })
                        })

                        let buyDom = confirmListDom.item(0);
                        let abandonDom = confirmListDom.item(1);
                        playerListDom.appendChild(playerFragment)
                        const buyFunc = async () => {
                            let selectedItem = playerOperateDom.querySelectorAll('.select-item.active')
                            let riches = 0;
                            let happy = 0;
                            let repute = 0;


                            selectedItem.forEach(selected => {
                                riches += selected.hasAttribute('data-riches') ? +selected.getAttribute('data-riches') : 0;
                                happy += selected.hasAttribute('data-happy') ? +selected.getAttribute('data-happy') : 0;
                                repute += selected.hasAttribute('data-repute') ? +selected.getAttribute('data-repute') : 0;
                            })

                            resultProxy[index] = {
                                player,
                                changed: {
                                    riches,
                                    happy,
                                    repute
                                }
                            }
                            playerOperateDom.querySelectorAll('.select-item:not(.active)').forEach(selectedDom => selectedDom.remove())
                            operateItemDom.innerHTML = `许愿`
                        }
                        buyDom.addEventListener("click", buyFunc)
                        const abandonFunc = () => {
                            operateItemDom.innerHTML = `放弃`
                            resultProxy[index] = null
                        }
                        abandonDom.addEventListener("click", abandonFunc)
                    })
                })
            }

            return {
                fragment,
                open
            }
        }();


        /**
         * 出行方式
         * @author 	 linyisonger
         * @date 	 2024-12-26
         */
        const travelModeContainer = await async function () {
            const fragment = templateLoad("#travel-mode-template")
            const travelModeBtnList = fragment.shadowRoot.querySelectorAll('.travel-mode-btn')
            const diceModeBtn = travelModeBtnList.item(0)
            const cardModeBtn = travelModeBtnList.item(1)
            fragment.classList.add('close')
            const open = function () {
                return new Promise((resolve) => {
                    fragment.classList.remove('close')
                    const useDiceFunc = function () {
                        fragment.classList.add('close')
                        resolve(false)
                    }
                    const useCardFunc = function () {
                        fragment.classList.add('close')
                        resolve(true)
                    }
                    diceModeBtn.removeEventListener("click", useDiceFunc)
                    cardModeBtn.removeEventListener("click", useCardFunc)
                    diceModeBtn.addEventListener("click", useDiceFunc)
                    cardModeBtn.addEventListener("click", useCardFunc)
                })

            }

            return {
                fragment,
                open
            }
        }()


        /**
         * 初始化游戏内容
         * @author 	 linyisonger
         * @date 	 2024-11-29
         */
        async function initGame() {
            let gameContainerDom = document.body.querySelector('.game-container')
            gameContainerDom.appendChild(mapContainer.fragment)
            gameContainerDom.appendChild(logContainer.fragment)
            gameContainerDom.appendChild(dialogContainer.fragment)
            gameContainerDom.appendChild(diceContainer.fragment)
            gameContainerDom.appendChild(roleContainer.fragment)
            gameContainerDom.appendChild(playerContainer.fragment)
            gameContainerDom.appendChild(hospitalContainer.fragment)
            gameContainerDom.appendChild(parkContainer.fragment)
            gameContainerDom.appendChild(lotteryContainer.fragment)
            gameContainerDom.appendChild(stockContainer.fragment)
            gameContainerDom.appendChild(mallContainer.fragment)
            gameContainerDom.appendChild(galleryContainer.fragment)
            gameContainerDom.appendChild(wishingContainer.fragment)
            gameContainerDom.appendChild(chanceContainer.fragment)
            gameContainerDom.appendChild(cardHolderContainer.fragment)
            gameContainerDom.appendChild(travelModeContainer.fragment)


            document.body.appendChild(gameContainerDom)
            let selecedRoles = await roleContainer.open()
            // 选中的角色
            // let selecedRoles = [
            //     {
            //         "name": "小黑",
            //         "avatar": "./assets/monopoly/black.jpg",
            //         "color": "black",
            //         "targetRiches": 60,
            //         "targetRepute": 0,
            //         "targetHapply": 0
            //     },
            //     {
            //         "name": "小红",
            //         "avatar": "./assets/monopoly/red.jpg",
            //         "color": "red",
            //         "targetRiches": 60,
            //         "targetRepute": 0,
            //         "targetHapply": 0
            //     }
            // ]
            let players = selecedRoles.map(r => new Player(r))
            // 掷骰子
            // let diceTotal = await diceContainer.open(1)

            // 选择角色
            // let selecedRoles = await roleContainer.open()

            // 医院测试
            // let dictTotal = await hospitalContainer.open()

            // 公园测试
            // let parkTotal = await parkContainer.open()

            // 彩票测试
            // let lotteryResult = await lotteryContainer.open(players)

            // 证券市场
            // let stockResult = await stockContainer.open(players)

            // 商场
            // let mallResult = await mallContainer.open(players)
            // console.log(mallResult);

            // 画廊
            // let galleryResult = await galleryContainer.open(players)
            // console.log(galleryResult);

            // 机会
            // let chanceResult = await chanceContainer.open(players[0], ChanceCardType.CHANCE, 2)
            // console.log(chanceResult);

            // 许愿井
            // let wishingResult = await wishingContainer.open(players)
            // console.log(wishingResult);

            // 卡包
            // let cardHolderResult = await cardHolderContainer.open(players[0])
            // console.log(cardHolderResult);


            // 出行方式
            // let travelModeResult = await travelModeContainer.open();
            // console.log(travelModeResult);


            // 弹窗
            // let selected = await dialogContainer.open({
            //     title: '提示',
            //     content: '请选择想要的选项',
            //     options: ["A", "B", "C"]
            // })
            // console.log(selected);

            // 队列
            const queue = {
                players: [], // 玩家列表
                join: function (players) {
                    this.players = players;
                    playerContainer.join(players)
                    mapContainer.join(players)
                    return this;
                },
                next: async function () {
                    /** @type {Player} */
                    let player = this.players.shift()

                    // 暂停一轮
                    if (player.actionState == PlayerActionEnum.PAUSE) {
                        player.actionState = PlayerActionEnum.NONE;
                        this.players.push(player)
                        this.next();
                        return;
                    }

                    mapContainer.next(player)
                    playerContainer.next(player)

                    // 判断当前玩家是否可以移动  例如特定场地
                    let allowMove = await mapContainer.leave(player)
                    if (allowMove) {
                        console.log(player);
                        // 使用卡片还是投掷骰子
                        let hasCard = player.cardHolder.find(card => !card.passive)
                        let useCard = hasCard && await travelModeContainer.open();
                        let useDice = true;

                        if (useCard) {
                            let cardHolderResult = await cardHolderContainer.open(player, players)
                            useDice = cardHolderResult.useDice
                        }

                        if (useDice) {
                            logContainer.log(`玩家[${player.name}]开始掷骰子🎲`)
                            let dictCount = player.positionType == PositionTypeEnum.LOOP ? 2 : 1; // 骰子数量
                            let dictTotal = await diceContainer.open(dictCount);
                            logContainer.log(`玩家[${player.name}]投掷数为[${dictTotal}]`)
                            await mapContainer.move(player, dictTotal)
                        }
                    }

                    // 检查玩家破产和胜利
                    let allPlayers = [player, ...players]
                    for (let i = 0; i < allPlayers.length; i++) {
                        await playerContainer.checkBankruptcy(allPlayers[i])
                    }

                    // 检查玩家是否胜利
                    await playerContainer.checkWin(allPlayers)
                    // 再来一次
                    if (player.actionState === PlayerActionEnum.AGAIN) {
                        player.actionState = PlayerActionEnum.NONE;
                        this.players.unshift(player)
                    }
                    else this.players.push(player);
                    this.next()
                },
            }

            queue.join(players).next()
        }

        initGame();

    </script>
</body>

</html>